Initial commit

This commit is contained in:
Brian Harris
2012-11-26 12:58:24 -06:00
parent a5214f79ef
commit 5016f605b8
1115 changed files with 587266 additions and 0 deletions
+488
View File
@@ -0,0 +1,488 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idLib/precompiled.h"
#include "LightweightCompression.h"
/*
========================
HashIndex
========================
*/
static int HashIndex( int w, int k ) {
return ( w ^ k ) & idLZWCompressor::HASH_MASK;
}
/*
========================
idLZWCompressor::Start
========================
*/
void idLZWCompressor::Start( uint8 * data_, int maxSize_, bool append ) {
// Clear hash
ClearHash();
if ( append ) {
assert( lzwData->nextCode > LZW_FIRST_CODE );
int originalNextCode = lzwData->nextCode;
lzwData->nextCode = LZW_FIRST_CODE;
// If we are appending, then fill up the hash
for ( int i = LZW_FIRST_CODE; i < originalNextCode; i++ ) {
AddToDict( lzwData->dictionaryW[i], lzwData->dictionaryK[i] );
}
assert( originalNextCode == lzwData->nextCode );
} else {
for ( int i = 0; i < LZW_FIRST_CODE; i++ ) {
lzwData->dictionaryK[i] = (uint8)i;
lzwData->dictionaryW[i] = 0xFFFF;
}
lzwData->nextCode = LZW_FIRST_CODE;
lzwData->codeBits = LZW_START_BITS;
lzwData->codeWord = -1;
lzwData->tempValue = 0;
lzwData->tempBits = 0;
lzwData->bytesWritten = 0;
}
oldCode = -1; // Used by DecompressBlock
data = data_;
blockSize = 0;
blockIndex = 0;
bytesRead = 0;
maxSize = maxSize_;
overflowed = false;
savedBytesWritten = 0;
savedCodeWord = 0;
saveCodeBits = 0;
savedTempValue = 0;
savedTempBits = 0;
}
/*
========================
idLZWCompressor::ReadBits
========================
*/
int idLZWCompressor::ReadBits( int bits ) {
int bitsToRead = bits - lzwData->tempBits;
while ( bitsToRead > 0 ) {
if ( bytesRead >= maxSize ) {
return -1;
}
lzwData->tempValue |= (uint64)data[bytesRead++] << lzwData->tempBits;
lzwData->tempBits += 8;
bitsToRead -= 8;
}
int value = (int)lzwData->tempValue & ( ( 1 << bits ) - 1 );
lzwData->tempValue >>= bits;
lzwData->tempBits -= bits;
return value;
}
/*
========================
idLZWCompressor::WriteBits
========================
*/
void idLZWCompressor::WriteBits( uint32 value, int bits ) {
// Queue up bits into temp value
lzwData->tempValue |= (uint64)value << lzwData->tempBits;
lzwData->tempBits += bits;
// Flush 8 bits (1 byte) at a time ( leftovers will get caught in idLZWCompressor::End() )
while ( lzwData->tempBits >= 8 ) {
if ( lzwData->bytesWritten >= maxSize ) {
overflowed = true;
return;
}
data[lzwData->bytesWritten++] = (uint8)( lzwData->tempValue & 255 );
lzwData->tempValue >>= 8;
lzwData->tempBits -= 8;
}
}
/*
========================
idLZWCompressor::WriteChain
The chain is stored backwards, so we have to write it to a buffer then output the buffer in
reverse.
========================
*/
int idLZWCompressor::WriteChain( int code ) {
byte chain[lzwCompressionData_t::LZW_DICT_SIZE];
int firstChar = 0;
int i = 0;
do {
assert( i < lzwCompressionData_t::LZW_DICT_SIZE && code < lzwCompressionData_t::LZW_DICT_SIZE && code >= 0 );
chain[i++] = (byte)lzwData->dictionaryK[code];
code = lzwData->dictionaryW[code];
} while ( code != 0xFFFF );
firstChar = chain[--i];
for ( ; i >= 0; i-- ) {
block[blockSize++] = chain[i];
}
return firstChar;
}
/*
========================
idLZWCompressor::DecompressBlock
========================
*/
void idLZWCompressor::DecompressBlock() {
assert( blockIndex == blockSize ); // Make sure we've read all we can
blockIndex = 0;
blockSize = 0;
int firstChar = -1;
while ( blockSize < LZW_BLOCK_SIZE - lzwCompressionData_t::LZW_DICT_SIZE ) {
assert( lzwData->codeBits <= lzwCompressionData_t::LZW_DICT_BITS );
int code = ReadBits( lzwData->codeBits );
if ( code == -1 ) {
break;
}
if ( oldCode == -1 ) {
assert( code < 256 );
block[blockSize++] = (uint8)code;
oldCode = code;
firstChar = code;
continue;
}
if ( code >= lzwData->nextCode ) {
assert( code == lzwData->nextCode );
firstChar = WriteChain( oldCode );
block[blockSize++] = (uint8)firstChar;
} else {
firstChar = WriteChain( code );
}
AddToDict( oldCode, firstChar );
if ( BumpBits() ) {
oldCode = -1;
} else {
oldCode = code;
}
}
}
/*
========================
idLZWCompressor::ReadByte
========================
*/
int idLZWCompressor::ReadByte( bool ignoreOverflow ) {
if ( blockIndex == blockSize ) {
DecompressBlock();
}
if ( blockIndex == blockSize ) { //-V581 DecompressBlock() updates these values, the if() isn't redundant
if ( !ignoreOverflow ) {
overflowed = true;
assert( !"idLZWCompressor::ReadByte overflowed!" );
}
return -1;
}
return block[blockIndex++];
}
/*
========================
idLZWCompressor::WriteByte
========================
*/
void idLZWCompressor::WriteByte( uint8 value ) {
int code = Lookup( lzwData->codeWord, value );
if ( code >= 0 ) {
lzwData->codeWord = code;
} else {
WriteBits( lzwData->codeWord, lzwData->codeBits );
if ( !BumpBits() ) {
AddToDict( lzwData->codeWord, value );
}
lzwData->codeWord = value;
}
if ( lzwData->bytesWritten >= maxSize - ( lzwData->codeBits + lzwData->tempBits + 7 ) / 8 ) {
overflowed = true; // At any point, if we can't perform an End call, then trigger an overflow
return;
}
}
/*
========================
idLZWCompressor::Lookup
========================
*/
int idLZWCompressor::Lookup( int w, int k ) {
if ( w == -1 ) {
return k;
} else {
int i = HashIndex( w, k );
for ( int j = hash[i]; j != 0xFFFF; j = nextHash[j] ) {
assert( j < lzwCompressionData_t::LZW_DICT_SIZE );
if ( lzwData->dictionaryK[j] == k && lzwData->dictionaryW[j] == w ) {
return j;
}
}
}
return -1;
}
/*
========================
idLZWCompressor::AddToDict
========================
*/
int idLZWCompressor::AddToDict( int w, int k ) {
assert( w < 0xFFFF - 1 );
assert( k < 256 );
assert( lzwData->nextCode < lzwCompressionData_t::LZW_DICT_SIZE );
lzwData->dictionaryK[lzwData->nextCode] = (uint8)k;
lzwData->dictionaryW[lzwData->nextCode] = (uint16)w;
int i = HashIndex( w, k );
nextHash[lzwData->nextCode] = hash[i];
hash[i] = (uint16)lzwData->nextCode;
return lzwData->nextCode++;
}
/*
========================
idLZWCompressor::BumpBits
Possibly increments codeBits.
return: bool - true, if the dictionary was cleared.
========================
*/
bool idLZWCompressor::BumpBits() {
if ( lzwData->nextCode == ( 1 << lzwData->codeBits ) ) {
lzwData->codeBits ++;
if ( lzwData->codeBits > lzwCompressionData_t::LZW_DICT_BITS ) {
lzwData->nextCode = LZW_FIRST_CODE;
lzwData->codeBits = LZW_START_BITS;
ClearHash();
return true;
}
}
return false;
}
/*
========================
idLZWCompressor::End
========================
*/
int idLZWCompressor::End() {
assert( lzwData->tempBits < 8 );
assert( lzwData->bytesWritten < maxSize - ( lzwData->codeBits + lzwData->tempBits + 7 ) / 8 );
assert( ( Length() > 0 ) == ( lzwData->codeWord != -1 ) );
if ( lzwData->codeWord != -1 ) {
WriteBits( lzwData->codeWord, lzwData->codeBits );
}
if ( lzwData->tempBits > 0 ) {
if ( lzwData->bytesWritten >= maxSize ) {
overflowed = true;
return -1;
}
data[lzwData->bytesWritten++] = (uint8)lzwData->tempValue & ( ( 1 << lzwData->tempBits ) - 1 );
}
return Length() > 0 ? Length() : -1; // Total bytes written (or failure)
}
/*
========================
idLZWCompressor::Save
========================
*/
void idLZWCompressor::Save() {
assert( !overflowed );
// Check and make sure we are at a good spot (can call End)
assert( lzwData->bytesWritten < maxSize - ( lzwData->codeBits + lzwData->tempBits + 7 ) / 8 );
savedBytesWritten = lzwData->bytesWritten;
savedCodeWord = lzwData->codeWord;
saveCodeBits = lzwData->codeBits;
savedTempValue = lzwData->tempValue;
savedTempBits = lzwData->tempBits;
}
/*
========================
idLZWCompressor::Restore
========================
*/
void idLZWCompressor::Restore() {
lzwData->bytesWritten = savedBytesWritten;
lzwData->codeWord = savedCodeWord;
lzwData->codeBits = saveCodeBits;
lzwData->tempValue = savedTempValue;
lzwData->tempBits = savedTempBits;
}
/*
========================
idLZWCompressor::ClearHash
========================
*/
void idLZWCompressor::ClearHash() {
memset( hash, 0xFF, sizeof( hash ) );
}
/*
========================
idZeroRunLengthCompressor
Simple zero based run length encoder/decoder
========================
*/
void idZeroRunLengthCompressor::Start( uint8 * dest_, idLZWCompressor * comp_, int maxSize_ ) {
zeroCount = 0;
dest = dest_;
comp = comp_;
compressed = 0;
maxSize = maxSize_;
}
bool idZeroRunLengthCompressor::WriteRun() {
if ( zeroCount > 0 ) {
assert( zeroCount <= 255 );
if ( compressed + 2 > maxSize ) {
maxSize = -1;
return false;
}
if ( comp != NULL ) {
comp->WriteByte( 0 );
comp->WriteByte( (uint8)zeroCount );
} else {
*dest++ = 0;
*dest++ = (uint8)zeroCount;
}
compressed += 2;
zeroCount = 0;
}
return true;
}
bool idZeroRunLengthCompressor::WriteByte( uint8 value ) {
if ( value != 0 || zeroCount >= 255 ) {
if ( !WriteRun() ) {
maxSize = -1;
return false;
}
}
if ( value != 0 ) {
if ( compressed + 1 > maxSize ) {
maxSize = -1;
return false;
}
if ( comp != NULL ) {
comp->WriteByte( value );
} else {
*dest++ = value;
}
compressed++;
} else {
zeroCount++;
}
return true;
}
byte idZeroRunLengthCompressor::ReadByte() {
// See if we need to possibly read more data
if ( zeroCount == 0 ) {
int value = ReadInternal();
if ( value == -1 ) {
assert( 0 );
}
if ( value != 0 ) {
return (byte)value; // Return non zero values immediately
}
// Read the number of zeroes
zeroCount = ReadInternal();
}
assert( zeroCount > 0 );
zeroCount--;
return 0;
}
void idZeroRunLengthCompressor::ReadBytes( byte * dest, int count ) {
for ( int i = 0; i < count; i++ ) {
*dest++ = ReadByte();
}
}
void idZeroRunLengthCompressor::WriteBytes( uint8 * src, int count ) {
for ( int i = 0; i < count; i++ ) {
WriteByte( *src++ );
}
}
int idZeroRunLengthCompressor::End() {
WriteRun();
if ( maxSize == -1 ) {
return -1;
}
return compressed;
}
int idZeroRunLengthCompressor::ReadInternal() {
compressed++;
if ( comp != NULL ) {
return comp->ReadByte();
}
return *dest++;
}
+209
View File
@@ -0,0 +1,209 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __LIGHTWEIGHT_COMPRESSION_H__
#define __LIGHTWEIGHT_COMPRESSION_H__
struct lzwCompressionData_t {
static const int LZW_DICT_BITS = 12;
static const int LZW_DICT_SIZE = 1 << LZW_DICT_BITS;
uint8 dictionaryK[LZW_DICT_SIZE];
uint16 dictionaryW[LZW_DICT_SIZE];
int nextCode;
int codeBits;
int codeWord;
uint64 tempValue;
int tempBits;
int bytesWritten;
};
/*
========================
idLZWCompressor
Simple lzw based encoder/decoder
========================
*/
class idLZWCompressor {
public:
idLZWCompressor( lzwCompressionData_t * lzwData_ ) : lzwData( lzwData_ ) {}
static const int LZW_BLOCK_SIZE = ( 1 << 15 );
static const int LZW_START_BITS = 9;
static const int LZW_FIRST_CODE = ( 1 << ( LZW_START_BITS - 1 ) );
void Start( uint8 * data_, int maxSize, bool append = false );
int ReadBits( int bits );
int WriteChain( int code );
void DecompressBlock();
void WriteBits( uint32 value, int bits );
int ReadByte( bool ignoreOverflow = false );
void WriteByte( uint8 value );
int Lookup( int w, int k );
int AddToDict( int w, int k );
bool BumpBits();
int End();
int Length() const { return lzwData->bytesWritten; }
int GetReadCount() const { return bytesRead; }
void Save();
void Restore();
bool IsOverflowed() { return overflowed; }
int Write( const void * data, int length ) {
uint8 * src = (uint8*)data;
for ( int i = 0; i < length && !IsOverflowed(); i++ ) {
WriteByte( src[i] );
}
return length;
}
int Read( void * data, int length, bool ignoreOverflow = false ) {
uint8 * src = (uint8*)data;
for ( int i = 0; i < length; i++ ) {
int byte = ReadByte( ignoreOverflow );
if ( byte == -1 ) {
return i;
}
src[i] = (uint8)byte;
}
return length;
}
int WriteR( const void * data, int length ) {
uint8 * src = (uint8*)data;
for ( int i = 0; i < length && !IsOverflowed(); i++ ) {
WriteByte( src[length - i - 1] );
}
return length;
}
int ReadR( void * data, int length, bool ignoreOverflow = false ) {
uint8 * src = (uint8*)data;
for ( int i = 0; i < length; i++ ) {
int byte = ReadByte( ignoreOverflow );
if ( byte == -1 ) {
return i;
}
src[length - i - 1] = (uint8)byte;
}
return length;
}
template<class type> ID_INLINE size_t WriteAgnostic( const type & c ) {
return Write( &c, sizeof( c ) );
}
template<class type> ID_INLINE size_t ReadAgnostic( type & c, bool ignoreOverflow = false ) {
size_t r = Read( &c, sizeof( c ), ignoreOverflow );
return r;
}
static const int DICTIONARY_HASH_BITS = 10;
static const int MAX_DICTIONARY_HASH = 1 << DICTIONARY_HASH_BITS;
static const int HASH_MASK = MAX_DICTIONARY_HASH - 1;
private:
void ClearHash();
lzwCompressionData_t * lzwData;
uint16 hash[MAX_DICTIONARY_HASH];
uint16 nextHash[lzwCompressionData_t::LZW_DICT_SIZE];
// Used by DecompressBlock
int oldCode;
uint8 * data; // Read/write
int maxSize;
bool overflowed;
// For reading
int bytesRead;
uint8 block[LZW_BLOCK_SIZE];
int blockSize;
int blockIndex;
// saving/restoring when overflow (when writing).
// Must call End directly after restoring (dictionary is bad so can't keep writing)
int savedBytesWritten;
int savedCodeWord;
int saveCodeBits;
uint64 savedTempValue;
int savedTempBits;
};
/*
========================
idZeroRunLengthCompressor
Simple zero based run length encoder/decoder
========================
*/
class idZeroRunLengthCompressor {
public:
idZeroRunLengthCompressor() : zeroCount( 0 ), destStart( NULL ) {
}
void Start( uint8 * dest_, idLZWCompressor * comp_, int maxSize_ );
bool WriteRun();
bool WriteByte( uint8 value );
byte ReadByte();
void ReadBytes( byte * dest, int count );
void WriteBytes( uint8 * src, int count );
int End();
int CompressedSize() const { return compressed; }
private:
int ReadInternal();
int zeroCount; // Number of pending zeroes
idLZWCompressor * comp;
uint8 * destStart;
uint8 * dest;
int compressed; // Compressed size
int maxSize;
};
#endif // __LIGHTWEIGHT_COMPRESSION_H__
+590
View File
@@ -0,0 +1,590 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idLib/precompiled.h"
idCVar net_maxRate( "net_maxRate", "50", CVAR_INTEGER, "max send rate in kilobytes per second" );
idCVar net_showReliableCompression( "net_showReliableCompression", "0", CVAR_BOOL, "Show reliable compression ratio." );
// we use an assert(0); return idiom in some places, which lint complains about
//lint -e527 unreachable code at token 'return'
/*
================================================
idPacketProcessor::QueueReliableAck
================================================
*/
void idPacketProcessor::QueueReliableAck( int lastReliable ) {
// NOTE - Even if it was the last known sequence, go ahead and ack it, in case our last ack for this sequence got dropped
if ( lastReliable >= reliableSequenceRecv ) {
queuedReliableAck = lastReliable;
reliableSequenceRecv = lastReliable;
}
}
/*
================================================
idPacketProcessor::FinalizeRead
================================================
*/
int idPacketProcessor::FinalizeRead( idBitMsg & inMsg, idBitMsg & outMsg, int & userValue ) {
userValue = 0;
idInnerPacketHeader header;
header.ReadFromMsg( inMsg );
if ( !verify( header.Type() != PACKET_TYPE_FRAGMENTED ) ) { // We shouldn't be fragmented at this point
idLib::Printf("Received invalid fragmented packet.\n" );
return RETURN_TYPE_NONE;
}
if ( header.Type() == PACKET_TYPE_RELIABLE_ACK ) {
// Handle reliable ack
int reliableSequence = inMsg.ReadLong();
reliable.RemoveOlderThan( reliableSequence + 1 );
header.ReadFromMsg( inMsg ); // Read the new header, since the reliable ack sits on top the actual header of the message
}
if ( header.Type() == PACKET_TYPE_OOB ) {
// out-of-band packet
userValue = header.Value();
} else {
// At this point, this MUST be an in-band packet
if ( !verify( header.Type() == PACKET_TYPE_INBAND ) ) {
idLib::Printf("In-band packet expected, received type %i instead.\n", header.Type() );
return RETURN_TYPE_NONE;
}
// Reset number of reliables received (NOTE - This means you MUST unload all reliables as they are received)
numReliable = 0;
// Handle reliable portion of in-band packets
int numReliableRecv = header.Value();
int bufferPos = 0;
if ( numReliableRecv > 0 ) {
// Byte align msg
inMsg.ReadByteAlign();
int compressedSize = inMsg.ReadShort();
lzwCompressionData_t lzwData;
idLZWCompressor lzwCompressor( &lzwData );
lzwCompressor.Start( (uint8*)inMsg.GetReadData() + inMsg.GetReadCount(), compressedSize ); // Read from msg
int reliableSequence = 0;
lzwCompressor.ReadAgnostic< int >( reliableSequence );
for ( int r = 0; r < numReliableRecv; r++ ) {
uint8 uncompMem[ MAX_MSG_SIZE ];
uint16 reliableDataLength = 0;
lzwCompressor.ReadAgnostic< uint16 >( reliableDataLength );
lzwCompressor.Read( uncompMem, reliableDataLength );
if ( reliableSequence + r > reliableSequenceRecv ) { // Only accept newer reliable msg's than we've currently already received
if ( !verify( bufferPos + reliableDataLength <= sizeof( reliableBuffer ) ) ) {
idLib::Printf( "Reliable msg size overflow.\n" );
return RETURN_TYPE_NONE;
}
if ( !verify( numReliable < MAX_RELIABLE_QUEUE ) ) {
idLib::Printf( "Reliable msg count overflow.\n" );
return RETURN_TYPE_NONE;
}
memcpy( reliableBuffer + bufferPos, uncompMem, reliableDataLength );
reliableMsgSize[ numReliable ] = reliableDataLength;
reliableMsgPtrs[ numReliable++ ] = &reliableBuffer[ bufferPos ];
bufferPos += reliableDataLength;
} else {
extern idCVar net_verboseReliable;
if ( net_verboseReliable.GetBool() ) {
idLib::Printf( "Ignoring reliable msg %i because %i was already acked\n", ( reliableSequence + r ), reliableSequenceRecv );
}
}
if ( !verify( lzwCompressor.IsOverflowed() == false ) ) {
idLib::Printf( "lzwCompressor.IsOverflowed() == true.\n" );
return RETURN_TYPE_NONE;
}
}
inMsg.SetReadCount( inMsg.GetReadCount() + compressedSize );
QueueReliableAck( reliableSequence + numReliableRecv - 1 );
}
}
// Load actual msg
outMsg.BeginWriting();
outMsg.WriteData( inMsg.GetReadData() + inMsg.GetReadCount(), inMsg.GetRemainingData() );
outMsg.SetSize( inMsg.GetRemainingData() );
return ( header.Type() == PACKET_TYPE_OOB ) ? RETURN_TYPE_OOB : RETURN_TYPE_INBAND;
}
/*
================================================
idPacketProcessor::QueueReliableMessage
================================================
*/
bool idPacketProcessor::QueueReliableMessage( byte type, const byte * data, int dataLen ) {
return reliable.Append( reliableSequenceSend++, &type, 1, data, dataLen );
}
/*
========================
idPacketProcessor::CanSendMoreData
========================
*/
bool idPacketProcessor::CanSendMoreData() const {
if ( net_maxRate.GetInteger() == 0 ) {
return true;
}
return ( outgoingRateBytes <= net_maxRate.GetInteger() * 1024 );
}
/*
========================
idPacketProcessor::UpdateOutgoingRate
========================
*/
void idPacketProcessor::UpdateOutgoingRate( const int time, const int size ) {
outgoingBytes += size;
// update outgoing rate variables
if ( time > outgoingRateTime ) {
outgoingRateBytes -= outgoingRateBytes * (float)( time - outgoingRateTime ) / 1000.0f;
if ( outgoingRateBytes < 0.0f ) {
outgoingRateBytes = 0.0f;
}
}
outgoingRateTime = time;
outgoingRateBytes += size;
// compute an average bandwidth at intervals
if ( time - lastOutgoingRateTime > BANDWIDTH_AVERAGE_PERIOD ) {
currentOutgoingRate = 1000 * ( outgoingBytes - lastOutgoingBytes ) / ( time - lastOutgoingRateTime );
lastOutgoingBytes = outgoingBytes;
lastOutgoingRateTime = time;
}
}
/*
=================
idPacketProcessor::UpdateIncomingRate
=================
*/
void idPacketProcessor::UpdateIncomingRate( const int time, const int size ) {
incomingBytes += size;
// update incoming rate variables
if ( time > incomingRateTime ) {
incomingRateBytes -= incomingRateBytes * (float)( time - incomingRateTime ) / 1000.0f;
if ( incomingRateBytes < 0.0f ) {
incomingRateBytes = 0.0f;
}
}
incomingRateTime = time;
incomingRateBytes += size;
// compute an average bandwidth at intervals
if ( time - lastIncomingRateTime > BANDWIDTH_AVERAGE_PERIOD ) {
currentIncomingRate = 1000 * ( incomingBytes - lastIncomingBytes ) / ( time - lastIncomingRateTime );
lastIncomingBytes = incomingBytes;
lastIncomingRateTime = time;
}
}
/*
================================================
idPacketProcessor::ProcessOutgoing
NOTE - We only compress reliables because we assume everything else has already been compressed.
================================================
*/
bool idPacketProcessor::ProcessOutgoing( const int time, const idBitMsg & msg, bool isOOB, int userData ) {
// We can only do ONE ProcessOutgoing call, then we need to do GetSendFragment to
// COMPLETELY empty unsentMsg before calling ProcessOutgoing again.
if ( !verify( fragmentedSend == false ) ) {
idLib::Warning( "ProcessOutgoing: fragmentedSend == true!");
return false;
}
if ( !verify( unsentMsg.GetRemainingData() == 0 ) ) {
idLib::Warning( "ProcessOutgoing: unsentMsg.GetRemainingData() > 0!");
return false;
}
// Build the full msg to send, which could include reliable data
unsentMsg.InitWrite( unsentBuffer, sizeof( unsentBuffer ) );
unsentMsg.BeginWriting();
// Ack reliables if we need to (NOTE - We will send this ack on both the in-band and out-of-band channels)
if ( queuedReliableAck >= 0 ) {
idInnerPacketHeader header( PACKET_TYPE_RELIABLE_ACK, 0 );
header.WriteToMsg( unsentMsg );
unsentMsg.WriteLong( queuedReliableAck );
queuedReliableAck = -1;
}
if ( isOOB ) {
if ( msg.GetSize() + unsentMsg.GetSize() > MAX_OOB_MSG_SIZE ) { // Fragmentation not allowed for out-of-band msg's
idLib::Printf("Out-of-band packet too large %i\n", unsentMsg.GetSize() );
assert( 0 );
return false;
}
// We don't need to worry about reliable for out of band packets
idInnerPacketHeader header( PACKET_TYPE_OOB, userData );
header.WriteToMsg( unsentMsg );
} else {
// Add reliable msg's here if this is an in-band packet
idInnerPacketHeader header( PACKET_TYPE_INBAND, reliable.Num() );
header.WriteToMsg( unsentMsg );
if ( reliable.Num() > 0 ) {
// Byte align unsentMsg
unsentMsg.WriteByteAlign();
lzwCompressionData_t lzwData;
idLZWCompressor lzwCompressor( &lzwData );
lzwCompressor.Start( unsentMsg.GetWriteData() + unsentMsg.GetSize() + 2, unsentMsg.GetRemainingSpace() - 2 ); // Write to compressed mem, not exceeding MAX_MSG_SIZE (+2 to reserve space for compressed size)
int uncompressedSize = 4;
lzwCompressor.WriteAgnostic< int >( reliable.ItemSequence( 0 ) );
for ( int i = 0; i < reliable.Num(); i++ ) {
lzwCompressor.WriteAgnostic< uint16 >( reliable.ItemLength( i ) );
lzwCompressor.Write( reliable.ItemData( i ), reliable.ItemLength( i ) );
uncompressedSize += 2 + reliable.ItemLength( i );
}
lzwCompressor.End();
if ( lzwCompressor.IsOverflowed() ) {
idLib::Error( "reliable msg compressor overflow." );
}
unsentMsg.WriteShort( lzwCompressor.Length() );
unsentMsg.SetSize( unsentMsg.GetSize() + lzwCompressor.Length() );
if ( net_showReliableCompression.GetBool() ) {
static int totalUncompressed = 0;
static int totalCompressed = 0;
totalUncompressed += uncompressedSize;
totalCompressed += lzwCompressor.Length();
float ratio1 = (float)lzwCompressor.Length() / (float)uncompressedSize;
float ratio2 = (float)totalCompressed / (float)totalUncompressed;
idLib::Printf( "Uncompressed: %i, Compressed: %i, TotalUncompressed: %i, TotalCompressed: %i, (%2.2f / %2.2f )\n", uncompressedSize, lzwCompressor.Length(), totalUncompressed, totalCompressed, ratio1, ratio2 );
}
}
}
// Fill up with actual msg
unsentMsg.WriteData( msg.GetReadData(), msg.GetSize() );
if ( unsentMsg.GetSize() > MAX_PACKET_SIZE ) {
if ( isOOB ) {
idLib::Error( "oob msg's cannot fragment" );
}
fragmentedSend = true;
}
return true;
}
/*
================================================
idPacketProcessor::GetSendFragment
================================================
*/
bool idPacketProcessor::GetSendFragment( const int time, sessionId_t sessionID, idBitMsg & outMsg ) {
lastSendTime = time;
if ( unsentMsg.GetRemainingData() <= 0 ) {
return false; // Nothing to send
}
outMsg.BeginWriting();
idOuterPacketHeader outerHeader( sessionID );
// Write outer packet header to the msg
outerHeader.WriteToMsg( outMsg );
if ( !fragmentedSend ) {
// Simple case, no fragments to sent
outMsg.WriteData( unsentMsg.GetReadData(), unsentMsg.GetSize() );
unsentMsg.SetSize( 0 );
} else {
int currentSize = idMath::ClampInt( 0, MAX_PACKET_SIZE, unsentMsg.GetRemainingData() );
assert( currentSize > 0 );
assert( unsentMsg.GetRemainingData() - currentSize >= 0 );
// See if we'll have more fragments once we subtract off how much we're about to write
bool moreFragments = ( unsentMsg.GetRemainingData() - currentSize > 0 ) ? true : false;
if ( !unsentMsg.GetReadCount() ) { // If this is the first read, then we know it's the first fragment
assert( moreFragments ); // If we have a first, we must have more or something went wrong
idInnerPacketHeader header( PACKET_TYPE_FRAGMENTED, FRAGMENT_START );
header.WriteToMsg( outMsg );
} else {
idInnerPacketHeader header( PACKET_TYPE_FRAGMENTED, moreFragments ? FRAGMENT_MIDDLE : FRAGMENT_END );
header.WriteToMsg( outMsg );
}
outMsg.WriteLong( fragmentSequence );
outMsg.WriteData( unsentMsg.GetReadData() + unsentMsg.GetReadCount(), currentSize );
unsentMsg.ReadData( NULL, currentSize );
assert( moreFragments == unsentMsg.GetRemainingData() > 0 );
fragmentedSend = moreFragments;
fragmentSequence++; // Advance sequence
fragmentAccumulator++; // update the counter for the net debug hud
}
// The caller needs to send this packet, so assume he did, and update rates
UpdateOutgoingRate( time, outMsg.GetSize() );
return true;
}
/*
================================================
idPacketProcessor::ProcessIncoming
================================================
*/
int idPacketProcessor::ProcessIncoming( int time, sessionId_t expectedSessionID, idBitMsg & msg, idBitMsg & out, int & userData, const int peerNum ) {
assert( msg.GetSize() <= MAX_FINAL_PACKET_SIZE );
UpdateIncomingRate( time, msg.GetSize() );
idOuterPacketHeader outerHeader;
outerHeader.ReadFromMsg( msg );
sessionId_t sessionID = outerHeader.GetSessionID();
assert( sessionID == expectedSessionID );
if ( !verify( sessionID != SESSION_ID_CONNECTIONLESS_PARTY && sessionID != SESSION_ID_CONNECTIONLESS_GAME && sessionID != SESSION_ID_CONNECTIONLESS_GAME_STATE ) ) {
idLib::Printf( "Expected non connectionless ID, but got a connectionless one\n" );
return RETURN_TYPE_NONE;
}
if ( sessionID != expectedSessionID ) {
idLib::Printf( "Expected session id: %8x but got %8x instead\n", expectedSessionID, sessionID );
return RETURN_TYPE_NONE;
}
int c,b;
msg.SaveReadState( c, b );
idInnerPacketHeader header;
header.ReadFromMsg( msg );
if ( header.Type() != PACKET_TYPE_FRAGMENTED ) {
// Non fragmented
msg.RestoreReadState( c, b ); // Reset since we took a byte to check the type
return FinalizeRead( msg, out, userData );
}
// Decode fragmented packet
int readSequence = msg.ReadLong(); // Read sequence of fragment
if ( header.Value() == FRAGMENT_START ) {
msgWritePos = 0; // Reset msg reconstruction write pos
} else if ( fragmentSequence == -1 || readSequence != fragmentSequence + 1 ) {
droppedFrags++;
idLib::Printf( "Dropped Fragments - PeerNum: %i FragmentSeq: %i, ReadSeq: %i, Total: %i\n", peerNum, fragmentSequence, readSequence, droppedFrags );
// If this is the middle or end, make sure we are reading in fragmentSequence
fragmentSequence = -1;
return RETURN_TYPE_NONE; // Out of sequence
}
fragmentSequence = readSequence;
assert( msg.GetRemainingData() > 0 );
if ( !verify( msgWritePos + msg.GetRemainingData() < sizeof( msgBuffer ) ) ) {
idLib::Error( "ProcessIncoming: Fragmented msg buffer overflow." );
}
memcpy( msgBuffer + msgWritePos, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
msgWritePos += msg.GetRemainingData();
if ( header.Value() == FRAGMENT_END ) {
// Done reconstructing the msg
idBitMsg msg( msgBuffer, sizeof( msgBuffer ) );
msg.SetSize( msgWritePos );
return FinalizeRead( msg, out, userData );
}
if ( !verify( header.Value() == FRAGMENT_START || header.Value() == FRAGMENT_MIDDLE ) ) {
idLib::Printf( "ProcessIncoming: Invalid packet.\n" );
}
// If we get here, this is part (either beginning or end) of a fragmented packet.
// We return RETURN_TYPE_NONE to let the caller know they don't need to do anything yet.
return RETURN_TYPE_NONE;
}
/*
================================================
idPacketProcessor::ProcessConnectionlessOutgoing
================================================
*/
bool idPacketProcessor::ProcessConnectionlessOutgoing( idBitMsg & msg, idBitMsg & out, int lobbyType, int userData ) {
sessionId_t sessionID = lobbyType + 1;
// Write outer header
idOuterPacketHeader outerHeader( sessionID );
outerHeader.WriteToMsg( out );
// Write inner header
idInnerPacketHeader header( PACKET_TYPE_OOB, userData );
header.WriteToMsg( out );
// Write msg
out.WriteData( msg.GetReadData(), msg.GetSize() );
return true;
}
/*
================================================
idPacketProcessor::ProcessConnectionlessIncoming
================================================
*/
bool idPacketProcessor::ProcessConnectionlessIncoming( idBitMsg & msg, idBitMsg & out, int & userData ) {
idOuterPacketHeader outerHeader;
outerHeader.ReadFromMsg( msg );
sessionId_t sessionID = outerHeader.GetSessionID();
if ( sessionID != SESSION_ID_CONNECTIONLESS_PARTY && sessionID != SESSION_ID_CONNECTIONLESS_GAME && sessionID != SESSION_ID_CONNECTIONLESS_GAME_STATE ) {
// Not a connectionless msg (this can happen if a previously connected peer keeps sending data for whatever reason)
idLib::Printf( "ProcessConnectionlessIncoming: Invalid session ID - %d\n", sessionID );
return false;
}
idInnerPacketHeader header;
header.ReadFromMsg( msg );
if ( header.Type() != PACKET_TYPE_OOB ) {
idLib::Printf( "ProcessConnectionlessIncoming: header.Type() != PACKET_TYPE_OOB\n" );
return false; // Only out-of-band packets supported for connectionless
}
userData = header.Value();
out.BeginWriting();
out.WriteData( msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
out.SetSize( msg.GetRemainingData() );
return true;
}
/*
================================================
idPacketProcessor::GetSessionID
================================================
*/
idPacketProcessor::sessionId_t idPacketProcessor::GetSessionID( idBitMsg & msg ) {
sessionId_t sessionID;
int c,b;
msg.SaveReadState( c, b );
// Read outer header
idOuterPacketHeader outerHeader;
outerHeader.ReadFromMsg( msg );
// Get session ID
sessionID = outerHeader.GetSessionID();
msg.RestoreReadState( c, b );
return sessionID;
}
/*
================================================
idPacketProcessor::VerifyEmptyReliableQueue
================================================
*/
idCVar net_verifyReliableQueue( "net_verifyReliableQueue", "2", CVAR_INTEGER, "0: warn only, 1: error, 2: fixup, 3: fixup and verbose, 4: force test" );
#define RELIABLE_VERBOSE if ( net_verifyReliableQueue.GetInteger() >= 3 ) idLib::Printf
void idPacketProcessor::VerifyEmptyReliableQueue( byte keepMsgBelowThis, byte replaceWithThisMsg ) {
if ( net_verifyReliableQueue.GetInteger() == 4 ) {
RELIABLE_VERBOSE( "pushing a fake game reliable\n" );
const char * garbage = "garbage";
QueueReliableMessage( keepMsgBelowThis + 4, (const byte *)garbage, 8 );
QueueReliableMessage( replaceWithThisMsg, NULL, 0 );
}
if ( reliable.Num() == 0 ) {
return;
}
if ( net_verifyReliableQueue.GetInteger() == 1 ) {
idLib::Error( "reliable queue is not empty: %d messages", reliable.Num() );
return;
}
idLib::Warning( "reliable queue is not empty: %d messages", reliable.Num() );
if ( net_verifyReliableQueue.GetInteger() == 0 ) {
return;
}
// drop some stuff that is potentially dangerous and should not transmit
idDataQueue< MAX_RELIABLE_QUEUE, MAX_MSG_SIZE > clean;
RELIABLE_VERBOSE( "rollback send sequence from %d to %d\n", reliableSequenceSend, reliable.ItemSequence( 0 ) );
for ( int i = 0; i < reliable.Num(); i++ ) {
byte peek = reliable.ItemData( i )[0];
if ( peek < keepMsgBelowThis ) {
RELIABLE_VERBOSE( "keeping %d\n", peek );
clean.Append( reliable.ItemSequence( i ), reliable.ItemData( i ), reliable.ItemLength( i ) );
} else {
// Replace with fake msg, so we retain itemsequence ordering.
// If we don't do this, it's possible we remove the last msg, then append a single msg before the next send,
// and the client may think he already received the msg, since his last reliableSequenceRecv could be greater than our
// reliableSequenceSend if he already received the group of reliables we are mucking with
clean.Append( reliable.ItemSequence( i ), &replaceWithThisMsg, 1 );
RELIABLE_VERBOSE( "dropping %d\n", peek );
}
}
assert( reliable.Num() == clean.Num() );
reliable = clean;
}
+262
View File
@@ -0,0 +1,262 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __PACKET_PROCESSOR_H__
#define __PACKET_PROCESSOR_H__
/*
================================================
idPacketProcessor
================================================
*/
class idPacketProcessor {
public:
static const int RETURN_TYPE_NONE = 0;
static const int RETURN_TYPE_OOB = 1;
static const int RETURN_TYPE_INBAND = 2;
typedef uint16 sessionId_t;
static const int NUM_LOBBY_TYPE_BITS = 2;
static const int LOBBY_TYPE_MASK = ( 1 << NUM_LOBBY_TYPE_BITS ) - 1;
static const sessionId_t SESSION_ID_INVALID = 0;
static const sessionId_t SESSION_ID_CONNECTIONLESS_PARTY = 1;
static const sessionId_t SESSION_ID_CONNECTIONLESS_GAME = 2;
static const sessionId_t SESSION_ID_CONNECTIONLESS_GAME_STATE = 3;
static const int BANDWIDTH_AVERAGE_PERIOD = 250;
idPacketProcessor() {
Reset();
}
void Reset() {
msgWritePos = 0;
fragmentSequence = 0;
droppedFrags = 0;
fragmentedSend = false;
reliable = idDataQueue< MAX_RELIABLE_QUEUE, MAX_MSG_SIZE >();
reliableSequenceSend = 1;
reliableSequenceRecv = 0;
numReliable = 0;
queuedReliableAck = -1;
unsentMsg = idBitMsg();
lastSendTime = 0;
outgoingRateTime = 0;
outgoingRateBytes = 0.0f;
incomingRateTime = 0;
incomingRateBytes = 0.0f;
outgoingBytes = 0;
incomingBytes = 0;
currentOutgoingRate = 0;
lastOutgoingRateTime = 0;
lastOutgoingBytes = 0;
currentIncomingRate = 0;
lastIncomingRateTime = 0;
lastIncomingBytes = 0;
fragmentAccumulator = 0;
}
static const int MAX_MSG_SIZE = 8000; // This is the max size you can pass into ProcessOutgoing
static const int MAX_FINAL_PACKET_SIZE = 1200; // Lowest/safe MTU across all our platforms to avoid fragmentation at the transport layer (which is poorly supported by consumer hardware and may cause nasty latency side effects)
static const int MAX_RELIABLE_QUEUE = 64;
// TypeInfo doesn't like sizeof( sessionId_t )?? and then fails to understand the #ifdef/#else/#endif
//static const int MAX_PACKET_SIZE = MAX_FINAL_PACKET_SIZE - 6 - sizeof( sessionId_t ); // Largest possible packet before headers and such applied (subtract some for various internal header data, and session id)
static const int MAX_PACKET_SIZE = MAX_FINAL_PACKET_SIZE - 6 - 2; // Largest possible packet before headers and such applied (subtract some for various internal header data, and session id)
static const int MAX_OOB_MSG_SIZE = MAX_PACKET_SIZE - 1; // We don't allow fragmentation for out-of-band msg's, and we need a byte for the header
private:
void QueueReliableAck( int lastReliable );
int FinalizeRead( idBitMsg & inMsg, idBitMsg & outMsg, int & userValue );
public:
bool CanSendMoreData() const;
void UpdateOutgoingRate( const int time, const int size );
void UpdateIncomingRate( const int time, const int size );
void RefreshRates( int time ) { UpdateOutgoingRate( time, 0 ); UpdateIncomingRate( time, 0 ); }
// Used to queue reliable msg's, to be sent on the next ProcessOutgoing
bool QueueReliableMessage( byte type, const byte * data, int dataLen );
// Used to process a msg ready to be sent, could get fragmented into multiple fragments
bool ProcessOutgoing( const int time, const idBitMsg & msg, bool isOOB, int userData );
// Used to get each fragment for sending through the actual net connection
bool GetSendFragment( const int time, sessionId_t sessionID, idBitMsg & outMsg );
// Used to process a fragment received. Returns true when msg was reconstructed.
int ProcessIncoming( int time, sessionId_t expectedSessionID, idBitMsg & msg, idBitMsg & out, int & userData, const int peerNum );
// Returns true if there are more fragments to send
bool HasMoreFragments() const { return ( unsentMsg.GetRemainingData() > 0 ); }
// Num reliables not ack'd
int NumQueuedReliables() { return reliable.Num(); }
// True if we need to send a reliable ack
int NeedToSendReliableAck() { return queuedReliableAck >= 0 ? true : false; }
// Used for out-of-band non connected peers
// This doesn't actually support fragmentation, it is just simply here to hide the
// header structure, so the caller doesn't have to skip over the header data.
static bool ProcessConnectionlessOutgoing( idBitMsg & msg, idBitMsg & out, int lobbyType, int userData );
static bool ProcessConnectionlessIncoming( idBitMsg & msg, idBitMsg & out, int & userData );
// Used to "peek" at a session id of a message fragment
static sessionId_t GetSessionID( idBitMsg & msg );
int GetNumReliables() const { return numReliable; }
const byte * GetReliable( int i ) const { return reliableMsgPtrs[ i ]; }
int GetReliableSize( int i ) const { return reliableMsgSize[ i ]; }
void SetLastSendTime( int i ) { lastSendTime = i; }
int GetLastSendTime() const { return lastSendTime; }
float GetOutgoingRateBytes() const { return outgoingRateBytes; }
int GetOutgoingBytes() const { return outgoingBytes; }
float GetIncomingRateBytes() const { return incomingRateBytes; }
int GetIncomingBytes() const { return incomingBytes; }
// more reliable computation, based on a suitably small interval
int GetOutgoingRate2() const { return currentOutgoingRate; }
int GetIncomingRate2() const { return currentIncomingRate; }
// decrease a fragmentation counter, so we reflect how much we're maxing the MTU
bool TickFragmentAccumulator() { if ( fragmentAccumulator > 0 ) { fragmentAccumulator--; return true; } return false; }
int GetReliableDataSize() const { return reliable.GetDataLength(); }
void VerifyEmptyReliableQueue( byte keepMsgBelowThis, byte replaceWithThisMsg );
private:
// Packet header types
static const int PACKET_TYPE_INBAND = 0; // In-band. Number of reliable msg's stored in userData portion of header
static const int PACKET_TYPE_OOB = 1; // Out-of-band. userData free to use by the caller. Cannot fragment.
static const int PACKET_TYPE_RELIABLE_ACK = 2; // Header type used to piggy-back on top of msgs to ack reliable msg's
static const int PACKET_TYPE_FRAGMENTED = 3; // The msg is fragmented, fragment type stored in the userData portion of header
// PACKET_TYPE_FRAGMENTED userData values
static const int FRAGMENT_START = 0;
static const int FRAGMENT_MIDDLE = 1;
static const int FRAGMENT_END = 2;
class idOuterPacketHeader {
public:
idOuterPacketHeader() : sessionID( SESSION_ID_INVALID ) {}
idOuterPacketHeader( sessionId_t sessionID_ ) : sessionID( sessionID_ ) {}
void WriteToMsg( idBitMsg & msg ) {
msg.WriteUShort( sessionID );
}
void ReadFromMsg( idBitMsg & msg ) {
sessionID = msg.ReadUShort();
}
sessionId_t GetSessionID() { return sessionID; }
private:
sessionId_t sessionID;
};
class idInnerPacketHeader {
public:
idInnerPacketHeader() : type( 0 ), userData( 0 ) {}
idInnerPacketHeader( int inType, int inData ) : type( inType ), userData( inData ) {}
void WriteToMsg( idBitMsg & msg ) {
msg.WriteBits( type, 2 );
msg.WriteBits( userData, 6 );
}
void ReadFromMsg( idBitMsg & msg ) {
type = msg.ReadBits( 2 );
userData = msg.ReadBits( 6 );
}
int Type() { return type; }
int Value() { return userData; }
private:
int type;
int userData;
};
byte msgBuffer[ MAX_MSG_SIZE ]; // Buffer used to reconstruct the msg
int msgWritePos; // Write position into the msg reconstruction buffer
int fragmentSequence; // Fragment sequence number
int droppedFrags; // Number of dropped fragments
bool fragmentedSend; // Used to determine if the current send requires fragmenting
idDataQueue< MAX_RELIABLE_QUEUE, MAX_MSG_SIZE > reliable; // list of unacknowledged reliable messages
int reliableSequenceSend; // sequence number of the next reliable packet we're going to send to this peer
int reliableSequenceRecv; // sequence number of the last reliable packet we received from this peer
// These are for receiving reliables, you need to get these before the next process call or they will get cleared
int numReliable;
byte reliableBuffer[ MAX_MSG_SIZE ]; // We shouldn't have to hold more than this
const byte * reliableMsgPtrs[ MAX_RELIABLE_QUEUE ];
int reliableMsgSize[ MAX_RELIABLE_QUEUE ];
int queuedReliableAck; // Used to piggy back on the next send to ack reliables
idBitMsg unsentMsg;
byte unsentBuffer[ MAX_MSG_SIZE ]; // Buffer used hold the current msg until it's all sent
int lastSendTime;
// variables to keep track of the rate
int outgoingRateTime;
float outgoingRateBytes; // B/S
int incomingRateTime;
float incomingRateBytes; // B/S
int outgoingBytes;
int incomingBytes;
int currentOutgoingRate;
int lastOutgoingRateTime;
int lastOutgoingBytes;
int currentIncomingRate;
int lastIncomingRateTime;
int lastIncomingBytes;
int fragmentAccumulator; // counts max size packets we are sending for the net debug hud
};
#endif /* !__PACKET_PROCESSOR_H__ */
+1281
View File
File diff suppressed because it is too large Load Diff
+201
View File
@@ -0,0 +1,201 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SNAPSHOT_H__
#define __SNAPSHOT_H__
#include "snapshot_jobs.h"
extern idCVar net_verboseSnapshot;
#define NET_VERBOSESNAPSHOT_PRINT if ( net_verboseSnapshot.GetInteger() > 0 ) idLib::Printf
#define NET_VERBOSESNAPSHOT_PRINT_LEVEL( X, Y ) if ( net_verboseSnapshot.GetInteger() >= ( X ) ) idLib::Printf( Y )
/*
A snapshot contains a list of objects and their states
*/
class idSnapShot {
public:
idSnapShot();
idSnapShot( const idSnapShot & other );
~idSnapShot();
void operator=( const idSnapShot & other );
// clears the snapshot
void Clear();
int GetTime() const { return time; }
void SetTime( int t ) { time = t; }
int GetRecvTime() const { return recvTime; }
void SetRecvTime( int t ) { recvTime = t; }
// Loads only sequence and baseSequence values from the compressed stream
static void PeekDeltaSequence( const char * deltaMem, int deltaSize, int & sequence, int & baseSequence );
// Reads a new object state packet, which is assumed to be delta compressed against this snapshot
bool ReadDeltaForJob( const char * deltaMem, int deltaSize, int visIndex, idSnapShot * templateStates );
bool ReadDelta( idFile * file, int visIndex );
// Writes an object state packet which is delta compressed against the old snapshot
struct objectBuffer_t {
objectBuffer_t() : data( NULL ), size( 0 ) { }
objectBuffer_t( int s ) : data( NULL ), size( s ) { Alloc( s ); }
objectBuffer_t( const objectBuffer_t & o ) : data( NULL ), size( 0 ) { *this = o; }
~objectBuffer_t() { _Release(); }
void Alloc( int size );
int NumRefs() { return data == NULL ? 0 : data[size]; }
objectSize_t Size() const { return size; }
byte * Ptr() { return data == NULL ? NULL : data ; }
byte & operator[]( int i ) { return data[i]; }
void operator=( const objectBuffer_t & other );
// (not making private because of idSnapshot)
void _AddRef();
void _Release();
private:
byte * data;
objectSize_t size;
};
struct objectState_t {
objectState_t() :
objectNum( 0 ),
visMask( MAX_UNSIGNED_TYPE( uint32 ) ),
stale( false ),
deleted( false ),
changedCount( 0 ),
createdFromTemplate( false ),
expectedSequence( 0 )
{ }
void Print( const char * name );
uint16 objectNum;
objectBuffer_t buffer;
uint32 visMask;
bool stale; // easy way for clients to check if ss obj is stale. Probably temp till client side of vismask system is more fleshed out
bool deleted;
int changedCount; // Incremented each time the state changed
int expectedSequence;
bool createdFromTemplate;
};
struct submitDeltaJobsInfo_t {
objParms_t * objParms; // Start of object parms
int maxObjParms; // Max parms (which will dictate how many objects can be processed)
uint8 * objMemory; // Memory that objects were written out to
objHeader_t * headers; // Memory for headers
int maxHeaders;
int maxObjMemory; // Max memory (which will dictate when syncs need to occur)
lzwParm_t * lzwParms; // Start of lzw parms
int maxDeltaParms; // Max lzw parms (which will dictate how many syncs we can have)
idSnapShot * oldSnap; // snap we are comparing this snap to (to produce a delta)
int visIndex;
int baseSequence;
idSnapShot * templateStates; // states for new snapObj that arent in old states
lzwInOutData_t * lzwInOutData;
};
void SubmitWriteDeltaToJobs( const submitDeltaJobsInfo_t & submitDeltaJobInfo );
bool WriteDelta( idSnapShot & old, int visIndex, idFile * file, int maxLength, int optimalLength = 0 );
// Adds an object to the state, overwrites any existing object with the same number
objectState_t * S_AddObject( int objectNum, uint32 visMask, const idBitMsg & msg, const char * tag = NULL ) { return S_AddObject( objectNum, visMask, msg.GetReadData(), msg.GetSize(), tag ); }
objectState_t * S_AddObject( int objectNum, uint32 visMask, const byte * buffer, int size, const char * tag = NULL ) { return S_AddObject( objectNum, visMask, (const char *)buffer, size, tag ); }
objectState_t * S_AddObject( int objectNum, uint32 visMask, const char * buffer, int size, const char * tag = NULL );
bool CopyObject( const idSnapShot & oldss, int objectNum, bool forceStale = false );
int CompareObject( const idSnapShot * oldss, int objectNum, int start=0, int end=0, int oldStart=0 );
// returns the number of objects in this snapshot
int NumObjects() const { return objectStates.Num(); }
// Returns the object number of the specified object, also fills the bitmsg
int GetObjectMsgByIndex( int i, idBitMsg & msg, bool ignoreIfStale = false ) const;
// returns true if the object was found in the snapshot
bool GetObjectMsgByID( int objectNum, idBitMsg & msg, bool ignoreIfStale = false ) { return GetObjectMsgByIndex( FindObjectIndexByID( objectNum ), msg, ignoreIfStale ) == objectNum; }
// returns the object index or -1 if it's not found
int FindObjectIndexByID( int objectNum ) const;
// returns the object by id, or NULL if not found
objectState_t * FindObjectByID( int objectNum ) const;
// Returns whether or not an object is stale
bool ObjectIsStaleByIndex( int i ) const;
int ObjectChangedCountByIndex( int i ) const;
// clears the empty states from the snapshot snapshot
void CleanupEmptyStates();
void PrintReport();
void UpdateExpectedSeq( int newSeq );
void ApplyToExistingState( int objId, idBitMsg & msg );
objectState_t * GetTemplateState( int objNum, idSnapShot * templateStates, objectState_t * newState = NULL );
void RemoveObject( int objId );
private:
idList< objectState_t *, TAG_IDLIB_LIST_SNAPSHOT> objectStates;
idBlockAlloc< objectState_t, 16, TAG_NETWORKING > allocatedObjs;
int time;
int recvTime;
int BinarySearch( int objectNum ) const;
objectState_t & FindOrCreateObjectByID( int objectNum ); // objIndex is optional parm for returning the index of the obj
void SubmitObjectJob( const submitDeltaJobsInfo_t & submitDeltaJobsInfo, // Struct containing parameters originally passed in to SubmitWriteDeltaToJobs
objectState_t * newState, // New obj state (can be NULL, which means deleted)
objectState_t * oldState, // Old obj state (can be NULL, which means new)
objParms_t *& baseObjParm, // Starting obj parm of current stream
objParms_t *& curObjParm, // Current obj parm of current stream
objHeader_t *& curHeader, // Current header dest
uint8 *& curObjDest, // Current write pos of current obj
lzwParm_t *& curlzwParm ); // Current delta parm for next lzw job
void SubmitLZWJob(
const submitDeltaJobsInfo_t & writeDeltaInfo, // Struct containing parameters originally passed in to SubmitWriteDeltaToJobs
objParms_t *& baseObjParm, // Pointer to the first obj parm for the current stream
objParms_t *& curObjParm, // Current obj parm
lzwParm_t *& curlzwParm, // Current delta parm
bool saveDictionary // If true, this is the first of several calls which will be appended
);
void WriteObject( idFile * file, int visIndex, objectState_t * newState, objectState_t * oldState, int & lastobjectNum );
void FreeObjectState( int index );
};
#endif // __SNAPSHOT_H__
+524
View File
@@ -0,0 +1,524 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
idCVar net_optimalSnapDeltaSize( "net_optimalSnapDeltaSize", "1000", CVAR_INTEGER, "Optimal size of snapshot delta msgs." );
idCVar net_debugBaseStates( "net_debugBaseStates", "0", CVAR_BOOL, "Log out base state information" );
idCVar net_skipClientDeltaAppend( "net_skipClientDeltaAppend", "0", CVAR_BOOL, "Simulate delta receive buffer overflowing" );
/*
========================
idSnapshotProcessor::idSnapshotProcessor
========================
*/
idSnapshotProcessor::idSnapshotProcessor() {
//assert( mem.IsGlobalHeap() );
jobMemory = (jobMemory_t*)Mem_Alloc( sizeof( jobMemory_t) , TAG_NETWORKING );
assert_16_byte_aligned( jobMemory );
assert_16_byte_aligned( jobMemory->objParms.Ptr() );
assert_16_byte_aligned( jobMemory->headers.Ptr() );
assert_16_byte_aligned( jobMemory->lzwParms.Ptr() );
Reset( true );
}
/*
========================
idSnapshotProcessor::idSnapshotProcessor
========================
*/
idSnapshotProcessor::~idSnapshotProcessor() {
//mem.PushHeap();
Mem_Free( jobMemory );
//mem.PopHeap();
}
/*
========================
idSnapshotProcessor::Reset
========================
*/
void idSnapshotProcessor::Reset( bool cstor ) {
hasPendingSnap = false;
snapSequence = INITIAL_SNAP_SEQUENCE;
baseSequence = -1;
lastFullSnapBaseSequence = -1;
if ( !cstor && net_debugBaseStates.GetBool() ) {
idLib::Printf( "NET: Reset snapshot base");
}
baseState.Clear();
submittedState.Clear();
pendingSnap.Clear();
deltas.Clear();
partialBaseSequence = -1;
memset( &jobMemory->lzwInOutData, 0, sizeof( jobMemory->lzwInOutData ) );
}
/*
========================
idSnapshotProcessor::TrySetPendingSnapshot
========================
*/
bool idSnapshotProcessor::TrySetPendingSnapshot( idSnapShot & ss ) {
// Don't advance to the next snap until the last one was fully sent
if ( hasPendingSnap ) {
return false;
}
pendingSnap = ss;
hasPendingSnap = true;
return true;
}
/*
========================
idSnapshotProcessor::PeekDeltaSequence
========================
*/
void idSnapshotProcessor::PeekDeltaSequence( const char * deltaMem, int deltaSize, int & deltaSequence, int & deltaBaseSequence ) {
idSnapShot::PeekDeltaSequence( deltaMem, deltaSize, deltaSequence, deltaBaseSequence );
}
/*
========================
idSnapshotProcessor::ApplyDeltaToSnapshot
========================
*/
bool idSnapshotProcessor::ApplyDeltaToSnapshot( idSnapShot & snap, const char * deltaMem, int deltaSize, int visIndex ) {
return snap.ReadDeltaForJob( deltaMem, deltaSize, visIndex, &templateStates );
}
#ifdef STRESS_LZW_MEM
// When this defined, we'll stress the lzw compressor with the smallest possible buffer, and detect when we need to grow it to make
// sure we are gacefully detecting the situation.
static int g_maxlwMem = 100;
#endif
/*
========================
idSnapshotProcessor::SubmitPendingSnap
========================
*/
void idSnapshotProcessor::SubmitPendingSnap( int visIndex, uint8 * objMemory, int objMemorySize, lzwCompressionData_t * lzwData ) {
assert_16_byte_aligned( objMemory );
assert_16_byte_aligned( lzwData );
assert( hasPendingSnap );
assert( jobMemory->lzwInOutData.numlzwDeltas == 0 );
assert( net_optimalSnapDeltaSize.GetInteger() < jobMemory_t::MAX_LZW_MEM - 128 ); // Leave padding
jobMemory->lzwInOutData.lzwDeltas = jobMemory->lzwDeltas.Ptr();
jobMemory->lzwInOutData.maxlzwDeltas = jobMemory->lzwDeltas.Num();
jobMemory->lzwInOutData.lzwMem = jobMemory->lzwMem.Ptr();
#ifdef STRESS_LZW_MEM
jobMemory->lzwInOutData.maxlzwMem = g_maxlwMem;
#else
jobMemory->lzwInOutData.maxlzwMem = jobMemory_t::MAX_LZW_MEM;
#endif
jobMemory->lzwInOutData.lzwDmaOut = jobMemory_t::MAX_LZW_MEM;
jobMemory->lzwInOutData.numlzwDeltas = 0;
jobMemory->lzwInOutData.lzwBytes = 0;
jobMemory->lzwInOutData.optimalLength = net_optimalSnapDeltaSize.GetInteger();
jobMemory->lzwInOutData.snapSequence = snapSequence;
jobMemory->lzwInOutData.lastObjId = 0;
jobMemory->lzwInOutData.lzwData = lzwData;
idSnapShot::submitDeltaJobsInfo_t submitInfo;
submitInfo.objParms = jobMemory->objParms.Ptr();
submitInfo.maxObjParms = jobMemory->objParms.Num();
submitInfo.headers = jobMemory->headers.Ptr();
submitInfo.maxHeaders = jobMemory->headers.Num();
submitInfo.objMemory = objMemory;
submitInfo.maxObjMemory = objMemorySize;
submitInfo.lzwParms = jobMemory->lzwParms.Ptr();
submitInfo.maxDeltaParms = jobMemory->lzwParms.Num();
// Use a copy of base state to avoid race conditions.
// The main thread could change it behind the jobs backs.
submittedState = baseState;
submittedTemplateStates = templateStates;
submitInfo.templateStates = &submittedTemplateStates;
submitInfo.oldSnap = &submittedState;
submitInfo.visIndex = visIndex;
submitInfo.baseSequence = baseSequence;
submitInfo.lzwInOutData = &jobMemory->lzwInOutData;
pendingSnap.SubmitWriteDeltaToJobs( submitInfo );
}
/*
========================
idSnapshotProcessor::GetPendingSnapDelta
========================
*/
int idSnapshotProcessor::GetPendingSnapDelta( byte * outBuffer, int maxLength ) {
assert( PendingSnapReadyToSend() );
if ( !verify( jobMemory->lzwInOutData.numlzwDeltas == 1 ) ) {
jobMemory->lzwInOutData.numlzwDeltas = 0;
return 0; // No more deltas left to send
}
assert( hasPendingSnap );
jobMemory->lzwInOutData.numlzwDeltas = 0;
int size = jobMemory->lzwDeltas[0].size;
if ( !verify( size != -1 ) ) {
#ifdef STRESS_LZW_MEM
if ( g_maxlwMem < MAX_LZW_MEM ) {
g_maxlwMem += 50;
g_maxlwMem = Min( g_maxlwMem, MAX_LZW_MEM );
return 0;
}
#endif
// This can happen if there wasn't enough maxlzwMem to process one full obj in a single delta
idLib::Error( "GetPendingSnapDelta: Delta failed." );
}
uint8 * deltaData = &jobMemory->lzwMem[jobMemory->lzwDeltas[0].offset];
int deltaSequence = 0;
int deltaBaseSequence = 0;
PeekDeltaSequence( (const char *)deltaData, size, deltaSequence, deltaBaseSequence );
// sanity check: does the compressed data we are about to send have the sequence number we expect
assert( deltaSequence == jobMemory->lzwDeltas[0].snapSequence );
if ( !verify( size <= maxLength ) ) {
idLib::Error( "GetPendingSnapDelta: Size overflow." );
}
// Copy to out buffer
memcpy( outBuffer, deltaData, size );
// Set the sequence to what this delta actually belongs to
assert( jobMemory->lzwDeltas[0].snapSequence == snapSequence + 1 );
snapSequence = jobMemory->lzwDeltas[0].snapSequence;
//idLib::Printf( "deltas Num: %i, Size: %i\n", deltas.Num(), deltas.GetDataLength() );
// Copy to delta buffer
// NOTE - We don't need to save this delta off if peer has already ack'd this basestate.
// This can happen due to the fact that we defer the processing of snap deltas on jobs.
// When we start processing a delta, we use the currently ack'd basestate. If while we were processing
// the delta, the client acks a new basestate, we can get into this situation. In this case, we simply don't
// store the delta, since it will just take up space, and just get removed anyways during ApplySnapshotDelta.
// (and cause lots of spam when it sees the delta's basestate doesn't match the current ack'd one)
if ( deltaBaseSequence >= baseSequence ) {
if ( !deltas.Append( snapSequence, deltaData, size ) ) {
int resendLength = deltas.ItemLength( deltas.Num() - 1 );
if ( !verify( resendLength <= maxLength ) ) {
idLib::Error( "GetPendingSnapDelta: Size overflow for resend." );
}
memcpy( outBuffer, deltas.ItemData( deltas.Num() - 1 ), resendLength );
size = -resendLength;
}
}
if ( jobMemory->lzwInOutData.fullSnap ) {
// We sent the full snap, we can stop sending this pending snap now...
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 5, va( " wrote enough deltas to a full snapshot\n" ) ); // FIXME: peer number?
hasPendingSnap = false;
partialBaseSequence = -1;
} else {
partialBaseSequence = deltaBaseSequence;
}
return size;
}
/*
========================
idSnapshotProcessor::IsBusyConfirmingPartialSnap
========================
*/
bool idSnapshotProcessor::IsBusyConfirmingPartialSnap() {
if ( partialBaseSequence != -1 && baseSequence <= partialBaseSequence ) {
return true;
}
return false;
}
/*
========================
idSnapshotProcessor::ReceiveSnapshotDelta
NOTE: we use ReadDeltaForJob twice, once to build the same base as the server (based on server acks, down ApplySnapshotDelta), and another time to apply the snapshot we just received
could we avoid the double apply by keeping outSnap cached in memory and avoid rebuilding it from a delta when the next one comes around?
========================
*/
bool idSnapshotProcessor::ReceiveSnapshotDelta( const byte * deltaData, int deltaLength, int visIndex, int & outSeq, int & outBaseSeq, idSnapShot & outSnap, bool & fullSnap ) {
fullSnap = false;
int deltaSequence = 0;
int deltaBaseSequence = 0;
// Get the sequence of this delta, and the base sequence it is delta'd from
PeekDeltaSequence( (const char *)deltaData, deltaLength, deltaSequence, deltaBaseSequence );
//idLib::Printf("Incoming snapshot: %i, %i\n", deltaSequence, deltaBaseSequence );
if ( deltaSequence <= snapSequence ) {
NET_VERBOSESNAPSHOT_PRINT( "Rejecting old delta: %d (snapSequence: %d \n", deltaSequence, snapSequence );
return false; // Completely reject older out of order deltas
}
// Bring the base state up to date with the basestate this delta was compared to
ApplySnapshotDelta( visIndex, deltaBaseSequence );
// Once we get here, our base state should be caught up to that of the server
assert( baseSequence == deltaBaseSequence );
// Save the new delta
if ( net_skipClientDeltaAppend.GetBool() || !deltas.Append( deltaSequence, deltaData, deltaLength ) ) {
// This can happen if the delta queues get desync'd between the server and client.
// With recent fixes, this should be extremely rare, or impossible.
// Just in case this happens, we can recover by assuming we didn't even receive this delta.
idLib::Printf( "NET: ReceiveSnapshotDelta: No room to append delta %d/%d \n", deltaSequence, deltaBaseSequence );
return false;
}
// Update our snapshot sequence number to the newer one we just got (now that it's safe)
snapSequence = deltaSequence;
if ( deltas.Num() > 10 ) {
NET_VERBOSESNAPSHOT_PRINT("NET: ReceiveSnapshotDelta: deltas.Num() > 10: %d\n ", deltas.Num() );
for ( int i=0; i < deltas.Num(); i++ ) {
NET_VERBOSESNAPSHOT_PRINT( "%d ", deltas.ItemSequence( i ) );
}
NET_VERBOSESNAPSHOT_PRINT( "\n" );
}
if ( baseSequence != deltaBaseSequence ) {
// NOTE - With recent fixes, this should no longer be possible unless the delta is trashed
// We should probably disconnect from the server when this happens now.
static bool failed = false;
if ( !failed ) {
idLib::Printf( "NET: incorrect base state? not sure how this can happen... baseSequence: %d deltaBaseSequence: %d \n", baseSequence, deltaBaseSequence );
}
failed = true;
return false;
}
// Copy out the current deltas sequence values to caller
outSeq = deltaSequence;
outBaseSeq = deltaBaseSequence;
if ( baseSequence < 50 && net_debugBaseStates.GetBool() ) {
idLib::Printf( "NET: Proper basestate... baseSequence: %d deltaBaseSequence: %d \n", baseSequence, deltaBaseSequence );
}
// Make a copy of the basestate the server used to create this delta, and then apply and return it
outSnap = baseState;
fullSnap = ApplyDeltaToSnapshot( outSnap, (const char *)deltaData, deltaLength, visIndex );
// We received a new delta
return true;
}
/*
========================
idSnapshotProcessor::ApplySnapshotDelta
Apply a snapshot delta to our current basestate, and make that the new base.
We can remove all deltas that refer to the basetate we just removed.
========================
*/
bool idSnapshotProcessor::ApplySnapshotDelta( int visIndex, int snapshotNumber ) {
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 6, va( "idSnapshotProcessor::ApplySnapshotDelta snapshotNumber: %d\n", snapshotNumber ) );
// Sanity check deltas
SanityCheckDeltas();
// dump any deltas older than the acknoweledged snapshot, which should only happen if there is packet loss
deltas.RemoveOlderThan( snapshotNumber );
if ( deltas.Num() == 0 || deltas.ItemSequence( 0 ) != snapshotNumber ) {
// this means the snapshot was either already acknowledged or came out of order
// On the server, this can happen because the client is continuously/redundantly sending acks
// Once the server has ack'd a certain base sequence, it will need to ignore all the redundant ones.
// On the client, this will only happen due to out of order, or dropped packets.
if ( !common->IsServer() ) {
// these should be printed every time on the clients
// printing on server is not useful / results in tons of spam
if ( deltas.Num() == 0 ) {
NET_VERBOSESNAPSHOT_PRINT("NET: Got snapshot but ignored... deltas.Num(): %d snapshotNumber: %d \n", deltas.Num(), snapshotNumber );
} else {
NET_VERBOSESNAPSHOT_PRINT("NET: Got snapshot but ignored... deltas.ItemSequence( 0 ): %d != snapshotNumber: %d \n ", deltas.ItemSequence( 0 ), snapshotNumber );
for ( int i=0; i < deltas.Num(); i++ ) {
NET_VERBOSESNAPSHOT_PRINT("%d ", deltas.ItemSequence(i) );
}
NET_VERBOSESNAPSHOT_PRINT("\n");
}
}
return false;
}
int deltaSequence = 0;
int deltaBaseSequence = 0;
PeekDeltaSequence( (const char *)deltas.ItemData( 0 ), deltas.ItemLength( 0 ), deltaSequence, deltaBaseSequence );
assert( deltaSequence == snapshotNumber ); // Make sure compressed sequence number matches that in data queue
assert( baseSequence == deltaBaseSequence ); // If this delta isn't based off of our currently ack'd basestate, something is trashed...
assert( deltaSequence > baseSequence );
if ( baseSequence != deltaBaseSequence ) {
// NOTE - This should no longer happen with recent fixes.
// We should probably disconnect from the server if this happens. (packets are trashed most likely)
NET_VERBOSESNAPSHOT_PRINT( "NET: Got snapshot %d but baseSequence does not match. baseSequence: %d deltaBaseSequence: %d. \n", snapshotNumber, baseSequence, deltaBaseSequence );
return false;
}
// Apply this delta to our base state
if ( ApplyDeltaToSnapshot( baseState, (const char *)deltas.ItemData( 0 ), deltas.ItemLength( 0 ), visIndex ) ) {
lastFullSnapBaseSequence = deltaSequence;
}
baseSequence = deltaSequence; // This is now our new base sequence
// Remove deltas that we no longer need
RemoveDeltasForOldBaseSequence();
// Sanity check deltas
SanityCheckDeltas();
return true;
}
/*
========================
idSnapshotProcessor::RemoveDeltasForOldBaseSequence
Remove deltas for basestate we no longer have. We know we can remove them, because we will never
be able to apply them, since the basestate needed to generate a full snap from these deltas is gone.
Ways we can get deltas based on basestate we no longer have:
1. Server sends sequence 50 based on 49. It then sends sequence 51 based on 49.
Client acks 50, server applies it to 49, 50 is new base state.
Server now has a delta sequence 51 based on 49 that it won't ever be able to apply (50 is new basestate).
This is annoying, because it makes a lot of our sanity checks incorrectly fire off for benign issues.
Here is a series of events that make the old ( baseSequence != deltaBaseSequence ) assert:
Server:
49->50, 49->51, ack 50, 50->52, ack 51 (bam), 50->53
Client
49->50, ack 50, 49->51, ack 51 (bam), 50->52, 50->53
The client above will ack 51, even though he can't even apply that delta. To get around this, we simply don't
allow delta to exist in the list, unless their basestate is the current basestate we maintain.
This allows us to put sanity checks in place that don't fire off during benign conditions, and allow us
to truly check for trashed conditions.
========================
*/
void idSnapshotProcessor::RemoveDeltasForOldBaseSequence() {
// Remove any deltas that would apply to the old base we no longer maintain
// (we will never be able to apply these, since we don't have that base anymore)
for ( int i = deltas.Num() - 1; i >= 0; i-- ) {
int deltaSequence = 0;
int deltaBaseSequence = 0;
baseState.PeekDeltaSequence( (const char *)deltas.ItemData( i ), deltas.ItemLength( i ), deltaSequence, deltaBaseSequence );
if ( deltaBaseSequence < baseSequence ) {
// Remove this delta, and all deltas before this one
deltas.RemoveOlderThan( deltas.ItemSequence( i ) + 1 );
break;
}
}
}
/*
========================
idSnapshotProcessor::SanityCheckDeltas
Make sure delta sequence and basesequence values are valid, and in order, etc
========================
*/
void idSnapshotProcessor::SanityCheckDeltas() {
int deltaSequence = 0;
int deltaBaseSequence = 0;
int lastDeltaSequence = -1;
int lastDeltaBaseSequence = -1;
for ( int i = 0; i < deltas.Num(); i++ ) {
baseState.PeekDeltaSequence( (const char *)deltas.ItemData( i ), deltas.ItemLength( i ), deltaSequence, deltaBaseSequence );
assert( deltaSequence == deltas.ItemSequence( i ) ); // Make sure delta stored in compressed form matches the one stored in the data queue
assert( deltaSequence > lastDeltaSequence ); // Make sure they are in order (we reject out of order sequences in ApplysnapshotDelta)
assert( deltaBaseSequence >= lastDeltaBaseSequence ); // Make sure they are in order (they can be the same, since base sequences don't change until they've been ack'd)
assert( deltaBaseSequence >= baseSequence ); // We should have removed old delta's that can no longer be applied
assert( deltaBaseSequence == baseSequence || deltaBaseSequence == lastDeltaSequence ); // Make sure we still have a base (or eventually will have) that we can apply this delta to
lastDeltaSequence = deltaSequence;
lastDeltaBaseSequence = deltaBaseSequence;
}
}
/*
========================
idSnapshotProcessor::AddSnapObjTemplate
========================
*/
void idSnapshotProcessor::AddSnapObjTemplate( int objID, idBitMsg & msg ) {
extern idCVar net_ssTemplateDebug;
idSnapShot::objectState_t * state = templateStates.S_AddObject( objID, MAX_UNSIGNED_TYPE( uint32 ), msg );
if ( verify( state != NULL ) ) {
if ( net_ssTemplateDebug.GetBool() ) {
idLib::PrintfIf( net_ssTemplateDebug.GetBool(), "InjectingSnapObjBaseState[%d] size: %d\n", objID, state->buffer.Size() );
state->Print( "BASE STATE" );
}
state->expectedSequence = snapSequence;
}
}
+148
View File
@@ -0,0 +1,148 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SNAP_PROCESSOR_H__
#define __SNAP_PROCESSOR_H__
/*
================================================
idSnapshotProcessor
================================================
*/
class idSnapshotProcessor {
public:
static const int INITIAL_SNAP_SEQUENCE = 42;
idSnapshotProcessor();
~idSnapshotProcessor();
void Reset( bool cstor = false );
// TrySetPendingSnapshot Sets the currently pending snap.
// No new snaps will be sent until this snap has been fully sent.
// Returns true of the newly supplied snapshot was accepted (there were no pending snaps)
bool TrySetPendingSnapshot( idSnapShot & ss );
// Peek into delta to get deltaSequence, and deltaBaseSequence
void PeekDeltaSequence( const char * deltaMem, int deltaSize, int & deltaSequence, int & deltaBaseSequence );
// Apply a delta to the supplied snapshot
bool ApplyDeltaToSnapshot( idSnapShot & snap, const char * deltaMem, int deltaSize, int visIndex );
// Attempts to write the currently pending snap to the supplied buffer, which can then be sent as an unreliable msg.
// SubmitPendingSnap will submit the pending snap to a job, so that it can be retrieved later for sending.
void SubmitPendingSnap( int visIndex, uint8 * objMemory, int objMemorySize, lzwCompressionData_t * lzwData );
// GetPendingSnapDelta
int GetPendingSnapDelta( byte * outBuffer, int maxLength );
// If PendingSnapReadyToSend is true, then GetPendingSnapDelta will return something to send
bool PendingSnapReadyToSend() const { return jobMemory->lzwInOutData.numlzwDeltas > 0; }
// When you call WritePendingSnapshot, and then send the resulting buffer as a unreliable msg, you will eventually
// receive this on the client. Call this function to receive and apply it to the base state, and possibly return a fully received snap
// to then apply to the client game state
bool ReceiveSnapshotDelta( const byte * deltaData, int deltaLength, int visIndex, int & outSeq, int & outBaseSeq, idSnapShot & outSnap, bool & fullSnap );
// Function to apply a received (or ack'd) delta to the base state
bool ApplySnapshotDelta( int visIndex, int snapshotNumber );
// Remove deltas for basestate we no longer have.
// We know we can remove them, because we will never be able to apply them, since
// the basestate needed to generate a full snap from these deltas is gone.
void RemoveDeltasForOldBaseSequence();
// Make sure delta sequence and basesequence values are valid, and in order, etc
void SanityCheckDeltas();
// HasPendingSnap will return true if there is more of the last TrySetPendingSnapshot to be sent
bool HasPendingSnap() const { return hasPendingSnap; }
idSnapShot * GetBaseState() { return &baseState; }
idSnapShot * GetPendingSnap(){ return &pendingSnap; }
int GetSnapSequence() { return snapSequence; }
int GetBaseSequence() { return baseSequence; }
int GetFullSnapBaseSequence() { return lastFullSnapBaseSequence; }
// This is used to ack the latest delta we have. If we have no deltas, we sent -1 to make sure
// Server knows we don't want to ack, since we are as up to date as we can be
int GetLastAppendedSequence() { return deltas.Num() == 0 ? -1 : deltas.ItemSequence( deltas.Num() - 1 ); }
int GetSnapQueueSize() { return deltas.Num(); }
bool IsBusyConfirmingPartialSnap();
void AddSnapObjTemplate( int objID, idBitMsg & msg );
static const int MAX_SNAPSHOT_QUEUE = 64;
private:
// Internal commands to set up, and flush the compressors
static const int MAX_SNAP_SIZE = idPacketProcessor::MAX_MSG_SIZE;
static const int MAX_SNAPSHOT_QUEUE_MEM = 64 * 1024; // 64k
// sequence number of the last snapshot we sent/received
// on the server, the sequencing is different for each network peer (net_verboseSnapshot 1)
// on the jobbed snapshot compression path, the sequence is incremented in NewLZWStream and pulled into this in idSnapshotProcessor::GetPendingSnapDelta
int snapSequence;
int baseSequence;
int lastFullSnapBaseSequence; // Latest base sequence number that is a full snap
idSnapShot baseState; // known snapshot base on the client
idDataQueue< MAX_SNAPSHOT_QUEUE, MAX_SNAPSHOT_QUEUE_MEM > deltas; // list of unacknowledged snapshot deltas
idSnapShot pendingSnap; // Current snap waiting to be fully sent
bool hasPendingSnap; // true if pendingSnap is still waiting to be sent
struct jobMemory_t {
static const int MAX_LZW_DELTAS = 1; // FIXME: cleanup the old multiple delta support completely
// @TODO this is a hack fix to allow online to load into coop (where there are lots of entities).
// The real solution should be coming soon.
// Doom MP: we encountered the same problem, going from 1024 to 4096 as well until a better solution is in place
// (initial, useless, exchange of func_statics is killing us)
static const int MAX_OBJ_PARMS = 4096;
static const int MAX_LZW_PARMS = 32;
static const int MAX_OBJ_HEADERS = 256;
static const int MAX_LZW_MEM = 1024 * 8; // 8k in the byte * lzwMem buffers, must be <= PS3_DMA_MAX
// Parm memory to jobs
idArray<objParms_t, MAX_OBJ_PARMS> objParms;
idArray<objHeader_t, MAX_OBJ_HEADERS> headers;
idArray<lzwParm_t, MAX_LZW_PARMS> lzwParms;
// Output memory from jobs
idArray<lzwDelta_t, MAX_LZW_DELTAS> lzwDeltas; // Info about each pending delta output from jobs
idArray<byte, MAX_LZW_MEM> lzwMem; // Memory for output from lzw jobs
lzwInOutData_t lzwInOutData; // In/Out data used so lzw data can persist across lzw jobs
};
jobMemory_t * jobMemory;
idSnapShot submittedState;
idSnapShot templateStates; // holds default snapshot states for some newly spawned object
idSnapShot submittedTemplateStates;
int partialBaseSequence;
};
#endif /* !__SNAP_PROCESSOR_H__ */
+424
View File
@@ -0,0 +1,424 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "Snapshot_Jobs.h"
uint32 SnapObjChecksum( const uint8 * data, int length ) {
extern unsigned long CRC32_BlockChecksum( const void *data, int length );
return CRC32_BlockChecksum( data, length );
}
/*
========================
ObjectsSame
========================
*/
ID_INLINE bool ObjectsSame( objJobState_t & newState, objJobState_t & oldState ) {
assert( newState.valid && oldState.valid );
assert( newState.objectNum == oldState.objectNum );
if ( newState.size != oldState.size ) {
//assert( newState.data != oldState.data) );
return false; // Can't match if sizes different
}
/*
if ( newState.data == oldState.data ) {
return true; // Definite match
}
*/
if ( memcmp( newState.data, oldState.data, newState.size ) == 0 ) {
return true; // Byte match, same
}
return false; // Not the same
}
/*
========================
SnapshotObjectJob
This job processes objects by delta comparing them, and then zrle encoding them to the dest stream
The dest stream is then eventually read by the lzw job, and then lzw compressed into the final delta packet
ready to be sent to peers.
========================
*/
void SnapshotObjectJob( objParms_t * parms ) {
int visIndex = parms->visIndex;
objJobState_t & newState = parms->newState;
objJobState_t & oldState = parms->oldState;
objHeader_t * header = parms->destHeader;
uint8 * dataStart = parms->dest;
assert( newState.valid || oldState.valid );
// Setup header
header->flags = 0;
header->size = newState.valid ? newState.size : 0;
header->csize = 0;
header->objID = -1; // Default to ack
header->data = dataStart;
assert( header->size <= MAX_UNSIGNED_TYPE( objectSize_t ) );
// Setup checksum and tag
#ifdef SNAPSHOT_CHECKSUMS
header->checksum = 0;
#endif
idZeroRunLengthCompressor rleCompressor;
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.valid && oldState.valid ) {
// 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.size == 0 ) != ( oldState.size == 0 );
if ( !oldVisible && !newVisible ) {
// object is stale and ack'ed for this client, write nothing (see 'same object' below)
header->flags |= OBJ_SAME;
return;
} else if ( oldVisible && !newVisible ) {
//SNAP_VERBOSE_PRINT( "object %d to client %d goes stale\n", newState->objectNum, visIndex );
visChange = true;
visSendState = false;
} else if ( !oldVisible && newVisible ) {
//SNAP_VERBOSE_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 && ObjectsSame( newState, oldState ) ) {
// same state, write nothing
header->flags |= OBJ_SAME;
return;
}
}
// Get the id of the object we are writing out
int32 objectNum = ( newState.valid ) ? newState.objectNum : oldState.objectNum;
// Write out object id
header->objID = objectNum;
if ( !newState.valid ) {
// Deleted, write 0 size
assert( oldState.valid );
header->flags |= OBJ_DELETED;
} else if ( !oldState.valid ) {
// New object, write out full state
assert( newState.valid );
// delta against an empty snap
rleCompressor.Start( dataStart, NULL, OBJ_DEST_SIZE_ALIGN16( newState.size ) );
rleCompressor.WriteBytes( newState.data, newState.size );
header->csize = rleCompressor.End();
header->flags |= OBJ_NEW;
if ( header->csize == -1 ) {
// Not enough space, don't compress, have lzw job do zrle compression instead
memcpy( dataStart, newState.data, newState.size );
}
} else {
// Compare to same obj id in different snapshot
assert( newState.objectNum == oldState.objectNum );
header->flags |= OBJ_DIFFERENT;
if ( visChange ) {
header->flags |= visSendState ? OBJ_VIS_NOT_STALE : OBJ_VIS_STALE;
}
if ( !visChange || visSendState ) {
int compareSize = Min( newState.size, oldState.size );
rleCompressor.Start( dataStart, NULL, OBJ_DEST_SIZE_ALIGN16( newState.size ) );
for ( int b = 0; b < compareSize; b++ ) {
byte delta = newState.data[b] - oldState.data[b];
rleCompressor.WriteByte( ( 0xFF + 1 + delta ) & 0xFF );
}
// Get leftover
int leftOver = newState.size - compareSize;
if ( leftOver > 0 ) {
rleCompressor.WriteBytes( newState.data + compareSize, leftOver );
}
header->csize = rleCompressor.End();
if ( header->csize == -1 ) {
// Not enough space, don't compress, have lzw job do zrle compression instead
for ( int b = 0; b < compareSize; b++ ) {
*dataStart++ = ( ( 0xFF + 1 + ( newState.data[b] - oldState.data[b] ) ) & 0xFF );
}
// Get leftover
int leftOver = newState.size - compareSize;
if ( leftOver > 0 ) {
memcpy( dataStart, newState.data + compareSize, leftOver );
}
}
}
}
assert( header->csize <= OBJ_DEST_SIZE_ALIGN16( header->size ) );
#ifdef SNAPSHOT_CHECKSUMS
if ( newState.valid ) {
assert( newState.size );
header->checksum = SnapObjChecksum( newState.data, newState.size );
}
#endif
}
/*
========================
FinishLZWStream
========================
*/
static void FinishLZWStream( lzwParm_t * parm, idLZWCompressor * lzwCompressor ) {
if ( lzwCompressor->IsOverflowed() ) {
lzwCompressor->Restore();
}
lzwDelta_t & pendingDelta = parm->ioData->lzwDeltas[parm->ioData->numlzwDeltas];
if ( lzwCompressor->End() == -1 ) {
// If we couldn't end the stream, notify the main thread
pendingDelta.offset = -1;
pendingDelta.size = -1;
pendingDelta.snapSequence = -1;
parm->ioData->numlzwDeltas++;
return;
}
int size = lzwCompressor->Length();
pendingDelta.offset = parm->ioData->lzwBytes; // Remember offset into buffer
pendingDelta.size = size; // Remember size
pendingDelta.snapSequence = parm->ioData->snapSequence; // Remember which snap sequence this delta belongs to
parm->ioData->lzwBytes += size;
parm->ioData->numlzwDeltas++;
}
/*
========================
NewLZWStream
========================
*/
static void NewLZWStream( lzwParm_t * parm, idLZWCompressor * lzwCompressor ) {
// Reset compressor
int maxSize = parm->ioData->maxlzwMem - parm->ioData->lzwBytes;
lzwCompressor->Start( &parm->ioData->lzwMem[parm->ioData->lzwBytes], maxSize );
parm->ioData->lastObjId = 0;
parm->ioData->snapSequence++;
lzwCompressor->WriteAgnostic( parm->ioData->snapSequence );
lzwCompressor->WriteAgnostic( parm->baseSequence );
lzwCompressor->WriteAgnostic( parm->curTime );
}
/*
========================
ContinueLZWStream
========================
*/
static void ContinueLZWStream( lzwParm_t * parm, idLZWCompressor * lzwCompressor ) {
// Continue compressor where we left off
int maxSize = parm->ioData->maxlzwMem - parm->ioData->lzwBytes;
lzwCompressor->Start( &parm->ioData->lzwMem[parm->ioData->lzwBytes], maxSize, true );
}
/*
========================
LZWJobInternal
This job takes a stream of objects, which should already be zrle compressed, and then lzw compresses them
and builds a final delta packet ready to be sent to peers.
========================
*/
void LZWJobInternal( lzwParm_t * parm, unsigned int dmaTag ) {
assert( parm->numObjects > 0 );
#ifndef ALLOW_MULTIPLE_DELTAS
if ( parm->ioData->numlzwDeltas > 0 ) {
// Currently, we don't use fragmented deltas.
// We only send the first one and rely on a full snap being sent to get the whole snap across
assert( parm->ioData->numlzwDeltas == 1 );
assert( !parm->ioData->fullSnap );
return;
}
#endif
assert( parm->ioData->lzwBytes < parm->ioData->maxlzwMem );
dmaTag = dmaTag;
ALIGN16( idLZWCompressor lzwCompressor( parm->ioData->lzwData ) );
if ( parm->fragmented ) {
// This packet was partially written out, we need to continue writing, using previous lzw dictionary values
ContinueLZWStream( parm, &lzwCompressor );
} else {
// We can start a new lzw dictionary
NewLZWStream( parm, &lzwCompressor );
}
int numChangedObjProcessed = 0;
for ( int i = 0; i < parm->numObjects; i++ ) {
// This will eventually be gracefully caught in SnapshotProcessor.cpp.
// It's nice to know right when it happens though, so you can inspect the situation.
assert( !lzwCompressor.IsOverflowed() || numChangedObjProcessed > 1 );
// First, see if we need to finish the current lzw stream
if ( lzwCompressor.IsOverflowed() || lzwCompressor.Length() >= parm->ioData->optimalLength ) {
FinishLZWStream( parm, &lzwCompressor );
// indicate how much needs to be DMA'ed back out
parm->ioData->lzwDmaOut = parm->ioData->lzwBytes;
#ifdef ALLOW_MULTIPLE_DELTAS
NewLZWStream( parm, &lzwCompressor );
#else
// Currently, we don't use fragmented deltas.
// We only send the first one and rely on a full snap being sent to get the whole snap across
assert( !parm->ioData->fullSnap );
assert( parm->ioData->numlzwDeltas == 1 );
return;
#endif
}
if ( numChangedObjProcessed > 0 ) {
// We should be at a good spot in the stream if we've written at least one obj without overflowing, so save it
lzwCompressor.Save();
}
// Get header
objHeader_t * header = &parm->headers[i];
if ( header->objID == -1 ) {
assert( header->flags & OBJ_SAME );
continue; // Don't send object (which means ack)
}
numChangedObjProcessed++;
// Write obj id as delta into stream
lzwCompressor.WriteAgnostic<uint16>( (uint16)( header->objID - parm->ioData->lastObjId ) );
parm->ioData->lastObjId = (uint16)header->objID;
// Check special stale/notstale flags
if ( header->flags & ( OBJ_VIS_STALE | OBJ_VIS_NOT_STALE ) ) {
// Write stale/notstale flag
objectSize_t value = ( header->flags & OBJ_VIS_STALE ) ? SIZE_STALE : SIZE_NOT_STALE;
lzwCompressor.WriteAgnostic<objectSize_t>( value );
}
if ( header->flags & OBJ_VIS_STALE ) {
continue; // Don't write out data for stale objects
}
if ( header->flags & OBJ_DELETED ) {
// Object was deleted
lzwCompressor.WriteAgnostic<objectSize_t>( 0 );
continue;
}
// Write size
lzwCompressor.WriteAgnostic<objectSize_t>( (objectSize_t)header->size );
// Get compressed data area
uint8 * compressedData = header->data;
if ( header->csize == -1 ) {
// Wasn't zrle compressed, zrle now while lzw'ing
idZeroRunLengthCompressor rleCompressor;
rleCompressor.Start( NULL, &lzwCompressor, 0xFFFF );
rleCompressor.WriteBytes( compressedData, header->size );
rleCompressor.End();
} else {
// Write out zero-rle compressed data
lzwCompressor.Write( compressedData, header->csize );
}
#ifdef SNAPSHOT_CHECKSUMS
// Write checksum
lzwCompressor.WriteAgnostic( header->checksum );
#endif
// This will eventually be gracefully caught in SnapshotProcessor.cpp.
// It's nice to know right when it happens though, so you can inspect the situation.
assert( !lzwCompressor.IsOverflowed() || numChangedObjProcessed > 1 );
}
if ( !parm->saveDictionary ) {
// Write out terminator
uint16 objectDelta = 0xFFFF - parm->ioData->lastObjId;
lzwCompressor.WriteAgnostic( objectDelta );
// Last stream
FinishLZWStream( parm, &lzwCompressor );
// indicate how much needs to be DMA'ed back out
parm->ioData->lzwDmaOut = parm->ioData->lzwBytes;
parm->ioData->fullSnap = true; // We sent a full snap
} else {
// the compressor did some work, wrote data to lzwMem, but since we didn't call FinishLZWStream to end the compression,
// we need to figure how much needs to be DMA'ed back out
assert( parm->ioData->lzwBytes == 0 ); // I don't think we ever hit this with lzwBytes != 0, but adding it just in case
parm->ioData->lzwDmaOut = parm->ioData->lzwBytes + lzwCompressor.Length();
}
assert( parm->ioData->lzwBytes < parm->ioData->maxlzwMem );
}
/*
========================
LZWJob
========================
*/
void LZWJob( lzwParm_t * parm ) {
LZWJobInternal( parm, 0 );
}
+127
View File
@@ -0,0 +1,127 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SNAPSHOT_JOBS_H__
#define __SNAPSHOT_JOBS_H__
#include "LightweightCompression.h"
//#define SNAPSHOT_CHECKSUMS
typedef int32 objectSize_t;
static const objectSize_t SIZE_STALE = MAX_TYPE( objectSize_t ); // Special size to indicate object went stale
static const objectSize_t SIZE_NOT_STALE = MAX_TYPE( objectSize_t ) - 1; // Special size to indicate object is no longer stale
static const int RLE_COMPRESSION_PADDING = 16; // Padding to accommodate possible enlargement due to zlre compression
// OBJ_DEST_SIZE_ALIGN16 returns the total space needed to store an object for reading/writing during jobs
#define OBJ_DEST_SIZE_ALIGN16( s ) ( ( ( s ) + 15 ) & ~15 )
static const uint32 OBJ_VIS_STALE = ( 1 << 0 ); // Object went stale
static const uint32 OBJ_VIS_NOT_STALE = ( 1 << 1 ); // Object no longer stale
static const uint32 OBJ_NEW = ( 1 << 2 ); // New object (not in the last snap)
static const uint32 OBJ_DELETED = ( 1 << 3 ); // Object was deleted (not going to be in the new snap)
static const uint32 OBJ_DIFFERENT = ( 1 << 4 ); // Objects are in both snaps, but different
static const uint32 OBJ_SAME = ( 1 << 5 ); // Objects are in both snaps, and are the same (we don't send these, which means ack)
// This struct is used to communicate data from the obj jobs to the lzw job
struct ALIGNTYPE16 objHeader_t {
int32 objID; // Id of object.
int32 size; // Size data object holds (will be 0 if the obj is being deleted)
int32 csize; // Size after zrle compression
uint32 flags; // Flags used to communicate state from obj job to lzw delta job
uint8 * data; // Data ptr to obj memory
#ifdef SNAPSHOT_CHECKSUMS
uint32 checksum; // Checksum before compression, used for sanity checking
#endif
};
struct objJobState_t {
uint8 valid;
uint8 * data;
uint16 size;
uint16 objectNum;
uint32 visMask;
};
// Input to initial jobs that produce delta'd zrle compressed versions of all the snap obj's
struct ALIGNTYPE16 objParms_t {
// Input
uint8 visIndex;
objJobState_t newState;
objJobState_t oldState;
// Output
objHeader_t * destHeader;
uint8 * dest;
};
// Output from the job that takes the results of the delta'd zrle obj's.
// This struct contains the start of where the final delta packet data is within lzwMem
struct ALIGNTYPE16 lzwDelta_t {
int offset; // Offset into lzwMem
int size;
int snapSequence;
};
// Struct used to maintain state that needs to persist across lzw jobs
struct ALIGNTYPE16 lzwInOutData_t {
int numlzwDeltas; // Num pending deltas written
bool fullSnap; // True if entire snap was written out in one delta
lzwDelta_t * lzwDeltas; // Info about each final delta packet written out
int maxlzwDeltas; // Max lzw deltas
uint8 * lzwMem; // Resulting final lzw delta packet data
int maxlzwMem; // Max size in bytes that can fit in lzwMem
int lzwDmaOut; // How much of lzwMem needs to be DMA'ed back out
int lzwBytes; // Final delta packet bytes written
int optimalLength; // Optimal length of lzw streams
int snapSequence;
uint16 lastObjId; // Last obj id written out
lzwCompressionData_t * lzwData;
};
// Input to the job that takes the results of the delta'd zrle obj's, and turns them into lzw delta packets
struct ALIGNTYPE16 lzwParm_t {
// Input
int numObjects; // Number of objects this job needs to process
objHeader_t * headers; // Object headers
int curTime; // Cur snap time
int baseTime; // Base snap time
int baseSequence;
bool saveDictionary;
bool fragmented; // This lzw stream should continue where the last one left off
// In/Out
lzwInOutData_t * ioData; // In/Out
};
extern void SnapshotObjectJob( objParms_t * parms );
extern void LZWJob( lzwParm_t * parm );
#endif // __SNAPSHOT_JOBS_H__
+48
View File
@@ -0,0 +1,48 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
/*
========================
idAchievementSystem::SyncAchievementBits
========================
*/
void idAchievementSystem::SyncAchievementBits( idLocalUser * user ) {
if ( user != NULL ) {
idArray< bool, idAchievementSystem::MAX_ACHIEVEMENTS > achievements;
if ( GetAchievementState( user, achievements ) ) {
for ( int i = 0; i < achievements.Num(); i++ ) {
if ( achievements[i] ) {
user->GetProfile()->SetAchievement( i );
}
}
}
}
}
+123
View File
@@ -0,0 +1,123 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_ACHIEVEMENTS_H__
#define __SYS_ACHIEVEMENTS_H__
class idLocalUser;
// data structure for online achievement entry descriptions
// this is used for testing purposes to make sure that the consoles
// achievement settings match the game's decls
struct achievementDescription_t {
void Clear() {
name[0] = '\0';
description[0] = '\0';
hidden = false;
};
char name[500];
char description[1000];
bool hidden;
};
/*
================================================
idAchievementSystem
================================================
*/
class idAchievementSystem {
public:
static const int MAX_ACHIEVEMENTS = 128; // This matches the max number of achievements bits in the profile
virtual ~idAchievementSystem() {}
// PC and PS3 initialize for the system, not for a particular controller
virtual void Init() {}
// PS3 has to wait to install the .TRP file until *after* we determine there's enough space, for consistent user messaging
virtual void Start() {}
// Do any necessary cleanup
virtual void Shutdown() {}
// Is the achievement system ready for requests
virtual bool IsInitialized() { return false; }
// Add a local user to the system
virtual void RegisterLocalUser( idLocalUser * user ) {}
// This is only necessary on the 360 right now, we need this because the 360 maintains a buffer of pending actions
// per user. If a user is removed from the system, we need to inform the system so it can cancel it's in flight actions
// and allow the buffers to be reused
virtual void RemoveLocalUser( idLocalUser * user ) {}
// Unlocks the achievement, all platforms silently fail if the achievement has already been unlocked
virtual void AchievementUnlock( idLocalUser * user, const int achievementID ) = 0;
// Puts the achievement back to its original state, platform implementation may not allow this
virtual void AchievementLock( idLocalUser * user, const int achievementID ) {}
// Puts alls achievements back to their original state, platform implementation may not allow this
virtual void AchievementLockAll( idLocalUser * user, const int maxId ) {}
// Should be done every frame
virtual void Pump() = 0;
// Cancels all in-flight achievements for all users if NULL, resets the system so a Init() must be re-issued
virtual void Reset( idLocalUser * user = NULL ) {}
// Cancels all in-flight achievements, not very useful on PC
virtual void Cancel( idLocalUser * user ) {}
// Retrieves textual information about a given achievement
// returns false if there was an error
virtual bool GetAchievementDescription( idLocalUser * user, const int id, achievementDescription_t & data ) const { return false; }
// How much storage is required
// returns false if there was an error
virtual bool GetRequiredStorage( uint64 & requiredSizeTrophiesBytes ) { requiredSizeTrophiesBytes = 0; return true; }
// Retrieves state about of all achievements cached locally (may not be online yet)
// returns false if there was an error
virtual bool GetAchievementState( idLocalUser * user, idArray< bool, idAchievementSystem::MAX_ACHIEVEMENTS > & achievements ) const { return false; }
// Sets state of all the achievements within list (for debug purposes only)
// returns false if there was an error
virtual bool SetAchievementState( idLocalUser * user, idArray< bool, idAchievementSystem::MAX_ACHIEVEMENTS > & achievements ) { return false; }
// You want to get the server's cached achievement status into the user because the profile may not have been
// saved with the achievement bits after an achievement was granted.
void SyncAchievementBits( idLocalUser * user );
protected:
// Retrieves the index from the local user list
int GetLocalUserIndex( idLocalUser * user ) const { return users.FindIndex( user ); }
idStaticList< idLocalUser *, MAX_LOCAL_PLAYERS > users;
};
#endif // __SYS_ACHIEVEMENTS_H__
+183
View File
@@ -0,0 +1,183 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "sys_lobby_backend.h"
#include "sys_dedicated_server_search.h"
/*
========================
idDedicatedServerSearch::idDedicatedServerSearch
========================
*/
idDedicatedServerSearch::idDedicatedServerSearch() :
callback( NULL ) {
}
/*
========================
idDedicatedServerSearch::~idDedicatedServerSearch
========================
*/
idDedicatedServerSearch::~idDedicatedServerSearch() {
if ( callback != NULL ) {
delete callback;
}
}
/*
========================
idDedicatedServerSearch::StartSearch
========================
*/
void idDedicatedServerSearch::StartSearch( const idCallback & cb ) {
Clear();
callback = cb.Clone();
}
/*
========================
idDedicatedServerSearch::Clear
========================
*/
void idDedicatedServerSearch::Clear() {
if ( callback != NULL ) {
delete callback;
callback = NULL;
}
list.Clear();
}
/*
========================
idDedicatedServerSearch::Clear
========================
*/
void idDedicatedServerSearch::HandleQueryAck( lobbyAddress_t & addr, idBitMsg & msg ) {
bool found = false;
// Find the server this ack belongs to
for ( int i = 0; i < list.Num(); i++ ) {
serverInfoDedicated_t & query = list[i];
if ( query.addr.Compare( addr ) ) {
// Found the server
found = true;
bool canJoin = msg.ReadBool();
if ( !canJoin ) {
// If we can't join this server, then remove it
list.RemoveIndex( i-- );
break;
}
query.serverInfo.Read( msg );
query.connectedPlayers.Clear();
for ( int i = 0; i < query.serverInfo.numPlayers; i++ ) {
idStr user;
msg.ReadString( user );
query.connectedPlayers.Append( user );
}
break;
}
}
if ( !found ) {
bool canJoin = msg.ReadBool();
if ( canJoin ) {
serverInfoDedicated_t newServer;
newServer.addr = addr;
newServer.serverInfo.Read( msg );
if ( newServer.serverInfo.serverName.IsEmpty() ) {
newServer.serverInfo.serverName = addr.ToString();
}
newServer.connectedPlayers.Clear();
for ( int i = 0; i < newServer.serverInfo.numPlayers; i++ ) {
idStr user;
msg.ReadString( user );
newServer.connectedPlayers.Append( user );
}
list.Append( newServer );
}
}
if ( callback != NULL ) {
callback->Call();
}
}
/*
========================
idDedicatedServerSearch::GetAddrAtIndex
========================
*/
bool idDedicatedServerSearch::GetAddrAtIndex( netadr_t & addr, int i ) {
if ( i >= 0 && i < list.Num() ) {
addr = list[i].addr.netAddr;
return true;
}
return false;
}
/*
========================
idDedicatedServerSearch::DescribeServerAtIndex
========================
*/
const serverInfo_t * idDedicatedServerSearch::DescribeServerAtIndex( int i ) const {
if ( i >= 0 && i < list.Num() ) {
return &list[i].serverInfo;
}
return NULL;
}
/*
========================
idDedicatedServerSearch::GetServerPlayersAtIndex
========================
*/
const idList< idStr > * idDedicatedServerSearch::GetServerPlayersAtIndex( int i ) const {
if ( i >= 0 && i < list.Num() ) {
return &list[i].connectedPlayers;
}
return NULL;
}
/*
========================
idDedicatedServerSearch::NumServers
========================
*/
int idDedicatedServerSearch::NumServers() const {
return list.Num();
}
+65
View File
@@ -0,0 +1,65 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __DEDICATEDSERVERSEARCH_H__
#define __DEDICATEDSERVERSEARCH_H__
/*
================================================
idDedicatedServerSearch
================================================
*/
class idDedicatedServerSearch {
public:
idDedicatedServerSearch();
~idDedicatedServerSearch();
void StartSearch( const idCallback & cb );
void Clear();
void HandleQueryAck( lobbyAddress_t & addr, idBitMsg & msg );
bool GetAddrAtIndex( netadr_t & addr, int i );
const serverInfo_t * DescribeServerAtIndex( int i ) const;
const idList< idStr > * GetServerPlayersAtIndex( int i ) const;
int NumServers() const;
private:
struct serverInfoDedicated_t {
lobbyAddress_t addr;
serverInfo_t serverInfo;
idList< idStr > connectedPlayers;
};
idList< serverInfoDedicated_t > list;
idCallback * callback;
};
#endif // __DEDICATEDSERVERSEARCH_H__
+209
View File
@@ -0,0 +1,209 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_LEADERBOARDS_H__
#define __SYS_LEADERBOARDS_H__
/*
================================================================================================
Stats (for achievements, matchmaking, etc.)
================================================================================================
*/
/*
================================================
systemStats_t
This is to give the framework the ability to deal with stat indexes that are
completely up to each game. The system needs to deal with some stat indexes for things like
the level for matchmaking, etc.
================================================
*/
/*
================================================================================================
Leaderboards
================================================================================================
*/
const int MAX_LEADERBOARDS = 256;
const int MAX_LEADERBOARD_COLUMNS = 16;
enum aggregationMethod_t {
AGGREGATE_MIN, // Write the new value if it is less than the existing value.
AGGREGATE_MAX, // Write the new value if it is greater than the existing value.
AGGREGATE_SUM, // Add the new value to the existing value and write the result.
AGGREGATE_LAST, // Write the new value.
};
enum rankOrder_t {
RANK_GREATEST_FIRST, // Rank the in descending order, greatest score is best score
RANK_LEAST_FIRST, // Rank the in ascending order, lowest score is best score
};
enum statsColumnDisplayType_t {
STATS_COLUMN_DISPLAY_NUMBER,
STATS_COLUMN_DISPLAY_TIME_MILLISECONDS,
STATS_COLUMN_DISPLAY_CASH,
STATS_COLUMN_NEVER_DISPLAY,
};
struct columnDef_t {
const char * locDisplayName;
int bits;
aggregationMethod_t aggregationMethod;
statsColumnDisplayType_t displayType;
};
extern struct leaderboardDefinition_t * registeredLeaderboards[MAX_LEADERBOARDS];
extern int numRegisteredLeaderboards;
struct leaderboardDefinition_t {
leaderboardDefinition_t() :
id ( -1 ),
numColumns( 0 ),
columnDefs( NULL ),
rankOrder( RANK_GREATEST_FIRST ),
supportsAttachments( false ),
checkAgainstCurrent( false ) {
}
leaderboardDefinition_t( int id_, int numColumns_, const columnDef_t * columnDefs_, rankOrder_t rankOrder_, bool supportsAttachments_, bool checkAgainstCurrent_ ) :
id ( id_ ),
numColumns( numColumns_ ),
columnDefs( columnDefs_ ),
rankOrder( rankOrder_ ),
supportsAttachments( supportsAttachments_ ),
checkAgainstCurrent( checkAgainstCurrent_ ) {
assert( numRegisteredLeaderboards < MAX_LEADERBOARDS );
registeredLeaderboards[numRegisteredLeaderboards++] = this;
}
int32 id;
int32 numColumns;
const columnDef_t * columnDefs;
rankOrder_t rankOrder;
bool supportsAttachments;
bool checkAgainstCurrent; // Compare column 0 with the currently stored leaderboard, and only submit the new leaderboard if the new column 0 is better
};
struct column_t {
column_t( int64 value_ ) : value( value_ ) {}
column_t() {}
int64 value;
};
/*
================================================================================================
Contains the Achievement and LeaderBoard free function declarations.
================================================================================================
*/
typedef int32 leaderboardHandle_t;
/*
================================================
idLeaderBoardEntry
================================================
*/
class idLeaderBoardEntry {
public:
static const int MAX_LEADERBOARD_COLUMNS = 16;
idStr username; // aka gamertag
int64 score;
int64 columns[ MAX_LEADERBOARD_COLUMNS ];
};
const leaderboardDefinition_t * Sys_FindLeaderboardDef( int id );
//------------------------
// leaderboardError_t
//------------------------
enum leaderboardError_t {
LEADERBOARD_ERROR_NONE,
LEADERBOARD_ERROR_FAILED, // General error occurred
LEADERBOARD_ERROR_NOT_ONLINE, // Not online to request leaderboards
LEADERBOARD_ERROR_BUSY, // Unable to download leaderboards right now (download already in progress)
LEADERBOARD_ERROR_INVALID_USER, // Unable to request leaderboards as the given user
LEADERBOARD_ERROR_INVALID_REQUEST, // The leaderboard request was invalid
LEADERBOARD_ERROR_DOWNLOAD, // An error occurred while downloading the leaderboard
LEADERBOARD_ERROR_MAX
};
/*
================================================
idLeaderboardCallback
================================================
*/
class idLeaderboardCallback : public idCallback {
public:
struct row_t {
bool hasAttachment;
int64 attachmentID;
idStr name;
int64 rank;
idArray<int64,MAX_LEADERBOARD_COLUMNS> columns;
long user_id;
// CSteamID user_id;
};
idLeaderboardCallback() : def( NULL ), startIndex( -1 ), localIndex( -1 ), numRowsInLeaderboard( -1 ), errorCode( LEADERBOARD_ERROR_NONE ) { }
virtual idLeaderboardCallback * Clone() const = 0;
// Used by the platform handlers to set data
void ResetRows() { rows.Clear(); }
void AddRow( const row_t & row ) { rows.Append( row ); }
void SetNumRowsInLeaderboard( int32 i ) { numRowsInLeaderboard = i; }
void SetDef( const leaderboardDefinition_t * def_ ) { def = def_; }
void SetStartIndex( int startIndex_ ) { startIndex = startIndex_; }
void SetLocalIndex( int localIndex_ ) { localIndex = localIndex_; }
void SetErrorCode( leaderboardError_t errorCode ) { this->errorCode = errorCode; }
// Used in user callback for information retrieval
const leaderboardDefinition_t * GetDef() const { return def; }
int GetStartIndex() const { return startIndex; }
const idList< row_t > & GetRows() const { return rows; }
int GetNumRowsInLeaderboard() const { return numRowsInLeaderboard; }
int GetLocalIndex() const { return localIndex; }
leaderboardError_t GetErrorCode() const { return this->errorCode; }
protected:
const leaderboardDefinition_t * def; // leaderboard def
int startIndex; // where the first row starts in the online leaderboard
int localIndex; // if player is in the rows, this is the offset of him within the returned rows
idList< row_t > rows; // leaderboard entries for the request
int numRowsInLeaderboard; // total number of rows in the online leaderboard
leaderboardError_t errorCode; // error, if any, that occurred during last operation
};
#endif // !__SYS_LEADERBOARDS_H__
File diff suppressed because it is too large Load Diff
+914
View File
@@ -0,0 +1,914 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#include "sys_lobby_backend.h"
#define INVALID_LOBBY_USER_NAME " " // Used to be "INVALID" but Sony might not like that.
class idSessionCallbacks;
class idDebugGraph;
/*
========================
idLobby
========================
*/
class idLobby : public idLobbyBase {
public:
idLobby();
enum lobbyType_t {
TYPE_PARTY = 0,
TYPE_GAME = 1,
TYPE_GAME_STATE = 2,
TYPE_INVALID = 0xff
};
enum lobbyState_t {
STATE_IDLE,
STATE_CREATE_LOBBY_BACKEND,
STATE_SEARCHING,
STATE_OBTAINING_ADDRESS,
STATE_CONNECT_HELLO_WAIT,
STATE_FINALIZE_CONNECT,
STATE_FAILED,
NUM_STATES
};
enum failedReason_t {
FAILED_UNKNOWN,
FAILED_CONNECT_FAILED,
FAILED_MIGRATION_CONNECT_FAILED,
};
void Initialize( lobbyType_t sessionType_, class idSessionCallbacks * callbacks );
void StartHosting( const idMatchParameters & parms );
void StartFinding( const idMatchParameters & parms_ );
void Pump();
void ProcessSnapAckQueue();
void Shutdown( bool retainMigrationInfo = false, bool skipGoodbye = false ); // Goto idle state
void HandlePacket( lobbyAddress_t & remoteAddress, idBitMsg fragMsg, idPacketProcessor::sessionId_t sessionID );
lobbyState_t GetState() { return state; }
virtual bool HasActivePeers() const;
virtual bool IsLobbyFull() const { return NumFreeSlots() == 0; }
int NumFreeSlots() const;
public:
enum reliablePlayerToPlayer_t {
//RELIABLE_PLAYER_TO_PLAYER_VOICE_EVENT,
RELIABLE_PLAYER_TO_PLAYER_GAME_DATA,
// Game messages would be reserved here in the same way that RELIABLE_GAME_DATA is.
// I'm worried about using up the 0xff values we have for reliable type, so I'm not
// going to reserve anything here just yet.
NUM_RELIABLE_PLAYER_TO_PLAYER,
};
enum reliableType_t {
RELIABLE_HELLO, // host to peer : connection established
RELIABLE_USER_CONNECTED, // host to peer : a new session user connected
RELIABLE_USER_DISCONNECTED, // host to peer : a session user disconnected
RELIABLE_START_LOADING, // host to peer : peer should begin loading the map
RELIABLE_LOADING_DONE, // peer to host : finished loading map
RELIABLE_IN_GAME, // peer to host : first full snap received, in game now
RELIABLE_SNAPSHOT_ACK, // peer to host : got a snapshot
RELIABLE_RESOURCE_ACK, // peer to host : got some new resources
RELIABLE_CONNECT_AND_MOVE_TO_LOBBY, // host to peer : connect to this server
RELIABLE_PARTY_CONNECT_OK, // host to peer
RELIABLE_PARTY_LEAVE_GAME_LOBBY, // host to peer : leave game lobby
RELIABLE_MATCH_PARMS, // host to peer : update in match parms
RELIABLE_UPDATE_MATCH_PARMS, // peer to host : peer updating match parms
// User join in progress msg's (join in progress for the party/game lobby, not inside a match)
RELIABLE_USER_CONNECT_REQUEST, // peer to host: local user wants to join session in progress
RELIABLE_USER_CONNECT_DENIED, // host to peer: user join session in progress denied (not enough slots)
// User leave in progress msg's (leave in progress for the party/game lobby, not inside a match)
RELIABLE_USER_DISCONNECT_REQUEST, // peer to host: request host to remove user from session
RELIABLE_KICK_PLAYER, // host to peer : kick a player
RELIABLE_MATCHFINISHED, // host to peer - Match is in post looking at score board
RELIABLE_ENDMATCH, // host to peer - End match, and go to game lobby
RELIABLE_ENDMATCH_PREMATURE, // host to peer - End match prematurely, and go to game lobby (onl possible in unrated/custom games)
RELIABLE_SESSION_USER_MODIFIED, // peer to host : user changed something (emblem, name, etc)
RELIABLE_UPDATE_SESSION_USER, // host to peers : inform all peers of the change
RELIABLE_HEADSET_STATE, // * to * : headset state change for user
RELIABLE_VOICE_STATE, // * to * : voice state changed for user pair (mute, unmute, etc)
RELIABLE_PING, // * to * : send host->peer, then reflected
RELIABLE_PING_VALUES, // host to peers : ping data from lobbyUser_t for everyone
RELIABLE_BANDWIDTH_VALUES, // peer to host: data back about bandwidth test
RELIABLE_ARBITRATE, // host to peer : start arbitration
RELIABLE_ARBITRATE_OK, // peer to host : ack arbitration request
RELIABLE_POST_STATS, // host to peer : here, write these stats now (hacky)
RELIABLE_MIGRATION_GAME_DATA, // host to peers: game data to use incase of a migration
RELIABLE_START_MATCH_GAME_LOBBY_HOST, // game lobby host to game state lobby host: start the match, since all players are in
RELIABLE_DUMMY_MSG, // used as a placeholder for old removed msg's
RELIABLE_PLAYER_TO_PLAYER_BEGIN,
// use reliablePlayerToPlayer_t
RELIABLE_PLAYER_TO_PLAYER_END = RELIABLE_PLAYER_TO_PLAYER_BEGIN + NUM_RELIABLE_PLAYER_TO_PLAYER,
// * to * : misc reliable game data above this
RELIABLE_GAME_DATA = RELIABLE_PLAYER_TO_PLAYER_END
};
// JGM: Reliable type in packet is a byte and there are a lot of reliable game messages.
// Feel free to bump this up since it's arbitrary anyway, but take a look at gameReliable_t.
// At the moment, both Doom and Rage have around 32 gameReliable_t values.
compile_time_assert( RELIABLE_GAME_DATA < 64 );
static const char * stateToString[ NUM_STATES ];
// Consts
static const int PEER_HEARTBEAT_IN_SECONDS = 5; // Make sure something was sent every 5 seconds, so we don't time out
static const int CONNECT_REQUEST_FREQUENCY_IN_SECONDS = 5; // Frequency at which we resend a request to connect to a server (will increase in frequency over time down to MIN_CONNECT_FREQUENCY_IN_SECONDS)
static const int MIN_CONNECT_FREQUENCY_IN_SECONDS = 1; // Min frequency of connection attempts
static const int MAX_CONNECT_ATTEMPTS = 5;
static const int BANDWIDTH_REPORTING_MAX = 10240; // make bps to report receiving (clamp if higher). For quantizing
static const int BANDWIDTH_REPORTING_BITS = 16; // number of bits to use for bandwidth reporting
static const int MAX_BPS_HISTORY = 32; // size of outgoing bps history to maintain for each client
static const int MAX_SNAP_SIZE = idPacketProcessor::MAX_MSG_SIZE;
static const int MAX_SNAPSHOT_QUEUE = 64;
static const int OOB_HELLO = 0;
static const int OOB_GOODBYE = 1;
static const int OOB_GOODBYE_W_PARTY = 2;
static const int OOB_GOODBYE_FULL = 3;
static const int OOB_RESOURCE_LIST = 4;
static const int OOB_VOICE_AUDIO = 5;
static const int OOB_MATCH_QUERY = 6;
static const int OOB_MATCH_QUERY_ACK = 7;
static const int OOB_SYSTEMLINK_QUERY = 8;
static const int OOB_MIGRATE_INVITE = 9;
static const int OOB_BANDWIDTH_TEST = 10;
enum connectionState_t {
CONNECTION_FREE = 0, // Free peer slot
CONNECTION_CONNECTING = 1, // Waiting for response from host for initial connection
CONNECTION_ESTABLISHED = 2, // Connection is established and active
};
struct peer_t {
peer_t() {
loaded = false;
inGame = false;
networkChecksum = 0;
lastSnapTime = 0;
snapHz = 0.0f;
numResources = 0;
lastHeartBeat = 0;
connectionState = CONNECTION_FREE;
packetProc = NULL;
snapProc = NULL;
nextPing = 0; // do it asap
lastPingRtt = 0;
sessionID = idPacketProcessor::SESSION_ID_INVALID;
startResourceLoadTime = 0;
nextThrottleCheck = 0;
maxSnapQueueSize = 0;
throttledSnapRate = 0;
pauseSnapshots = false;
receivedBps = -1.0f;
maxSnapBps = -1.0f;
receivedThrottle = 0;
receivedThrottleTime = 0;
throttleSnapsForXSeconds = 0;
recoverPing = 0;
failedPingRecoveries = 0;
rightBeforeSnapsPing = 0;
bandwidthTestLastSendTime = 0;
bandwidthSequenceNum = 0;
bandwidthTestBytes = 0;
bandwidthChallengeStartSendTime = 0;
bandwidthChallengeResults = false;
bandwidthChallengeSendComplete = false;
numSnapsSent = 0;
ResetConnectState();
};
void ResetConnectState() {
lastResourceTime = 0;
lastSnapTime = 0;
snapHz =
lastProcTime = 0;
lastInBandProcTime = 0;
lastFragmentSendTime = 0;
needToSubmitPendingSnap = false;
lastSnapJobTime = true;
startResourceLoadTime = 0;
receivedBps = -1.0;
maxSnapBps = -1.0f;
receivedThrottle = 0;
receivedThrottleTime = 0;
throttleSnapsForXSeconds = 0;
recoverPing = 0;
failedPingRecoveries = 0;
rightBeforeSnapsPing = 0;
bandwidthTestLastSendTime = 0;
bandwidthSequenceNum = 0;
bandwidthTestBytes = 0;
bandwidthChallengeStartSendTime = 0;
bandwidthChallengeResults = false;
bandwidthChallengeSendComplete = false;
memset( sentBpsHistory, 0, sizeof( sentBpsHistory ) );
receivedBpsIndex = 0;
debugGraphs.Clear();
}
void ResetAllData() {
ResetConnectState();
ResetMatchData();
}
void ResetMatchData() {
loaded = false;
networkChecksum = 0;
inGame = false;
numResources = 0;
needToSubmitPendingSnap = false;
throttledSnapRate = 0;
maxSnapQueueSize = 0;
receivedBpsIndex = -1;
numSnapsSent = 0;
pauseSnapshots = false;
// Reset the snapshot processor
if ( snapProc != NULL ) {
snapProc->Reset( false );
}
}
void Print() {
idLib::Printf(" lastResourceTime: %d\n", lastResourceTime );
idLib::Printf(" lastSnapTime: %d\n", lastSnapTime );
idLib::Printf(" lastProcTime: %d\n", lastProcTime );
idLib::Printf(" lastInBandProcTime: %d\n", lastInBandProcTime );
idLib::Printf(" lastFragmentSendTime: %d\n", lastFragmentSendTime );
idLib::Printf(" needToSubmitPendingSnap: %d\n", needToSubmitPendingSnap );
idLib::Printf(" lastSnapJobTime: %d\n", lastSnapJobTime );
}
bool IsActive() const { return connectionState != CONNECTION_FREE; }
bool IsConnected() const { return connectionState == CONNECTION_ESTABLISHED; }
connectionState_t GetConnectionState() const;
connectionState_t connectionState;
bool loaded; // true if this peer has finished loading the map
bool inGame; // true if this peer received the first snapshot, and is in-game
int lastSnapTime; // Last time a snapshot was sent on the network to this peer
float snapHz;
int lastProcTime; // Used to determine when a packet was processed for sending to this peer
int lastInBandProcTime; // Last time a in-band packet was processed for sending
int lastFragmentSendTime; // Last time a fragment was sent out (fragments are processed msg's, waiting to be fully sent)
unsigned long networkChecksum; // Checksum used to determine if a peer loaded the network resources the EXACT same as the server did
int pauseSnapshots;
lobbyAddress_t address;
int numResources; // number of network resources we know the peer has
idPacketProcessor * packetProc; // Processes packets for this peer
idSnapshotProcessor * snapProc; // Processes snapshots for this peer
idStaticList< idDebugGraph *, 4 > debugGraphs;//
int lastResourceTime; // Used to throttle the sending of resources
int lastHeartBeat;
int nextPing; // next Sys_Milliseconds when I'll send this peer a RELIABLE_PING
int lastPingRtt;
bool needToSubmitPendingSnap;
int lastSnapJobTime; // Last time a snapshot was sent to the joblist for this peer
int startResourceLoadTime; // Used to determine how long a peer has been loading resources
int maxSnapQueueSize; // how big has the snap queue gotten?
int throttledSnapRate; // effective snap rate for this peer
int nextThrottleCheck;
int numSnapsSent;
float sentBpsHistory[ MAX_BPS_HISTORY ];
int receivedBpsIndex;
float receivedBps; // peer's reported bps (they tell us their effective downstream)
float maxSnapBps;
float receivedThrottle; // amount of accumlated time this client has been lagging behind
int receivedThrottleTime; // last time we did received based throttle calculations
int throttleSnapsForXSeconds;
int recoverPing;
int failedPingRecoveries;
int rightBeforeSnapsPing;
int bandwidthChallengeStartSendTime; // time we sent first packet of bw challenge to this peer
int bandwidthTestLastSendTime; // last time in MS we sent them a bw challenge packet
int bandwidthTestBytes; // used to measure number of bytes we sent them
int bandwidthSequenceNum; // number of challenge sequences we sent them
bool bandwidthChallengeResults; // we got results back
bool bandwidthChallengeSendComplete; // we finished sending everything
idPacketProcessor::sessionId_t sessionID;
};
const char * GetLobbyName() {
switch ( lobbyType ) {
case TYPE_PARTY: return "TYPE_PARTY";
case TYPE_GAME: return "TYPE_GAME";
case TYPE_GAME_STATE: return "TYPE_GAME_STATE";
}
return "LOBBY_INVALID";
}
virtual lobbyUserID_t AllocLobbyUserSlotForBot( const char * botName ); // find a open user slot for the bot, and return the userID.
virtual void RemoveBotFromLobbyUserList( lobbyUserID_t lobbyUserID ); // release the session user slot, so that it can be claimed by a player, etc.
virtual bool GetLobbyUserIsBot( lobbyUserID_t lobbyUserID ) const; // check to see if the lobby user is a bot or not
virtual int GetNumLobbyUsers() const { return userList.Num(); }
virtual int GetNumActiveLobbyUsers() const;
virtual bool AllPeersInGame() const;
lobbyUser_t * GetLobbyUser( int index ) { return ( index >= 0 && index < GetNumLobbyUsers() ) ? userList[index] : NULL; }
const lobbyUser_t * GetLobbyUser( int index ) const { return ( index >= 0 && index < GetNumLobbyUsers() ) ? userList[index] : NULL; }
virtual bool IsLobbyUserConnected( int index ) const { return !IsLobbyUserDisconnected( index ); }
virtual int PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const;
virtual int GetPeerTimeSinceLastPacket( int peerIndex ) const;
virtual int PeerIndexForHost() const { return host; }
virtual int PeerIndexOnHost() const { return peerIndexOnHost; } // Returns -1 if we are the host
virtual const idMatchParameters & GetMatchParms() const { return parms; }
lobbyType_t GetActingGameStateLobbyType() const;
// If IsHost is true, we are a host accepting connections from peers
bool IsHost() const { return isHost; }
// If IsPeer is true, we are a peer, with an active connection to a host
bool IsPeer() const {
if ( host == -1 ) {
return false; // Can't possibly be a peer if we haven't setup a host
}
assert( !IsHost() );
return peers[host].IsConnected();
}
bool IsConnectingPeer() const {
if ( host == -1 ) {
return false; // Can't possibly be a peer if we haven't setup a host
}
assert( !IsHost() );
return peers[host].connectionState == CONNECTION_CONNECTING;
}
// IsRunningAsHostOrPeer means we are either an active host, and can accept connections from peers, or we are a peer with an active connection to a host
bool IsRunningAsHostOrPeer() const { return IsHost() || IsPeer(); }
bool IsLobbyActive() const { return IsRunningAsHostOrPeer(); }
struct reliablePlayerToPlayerHeader_t {
int fromSessionUserIndex;
int toSessionUserIndex;
reliablePlayerToPlayerHeader_t();
// Both read and write return false if the data is invalid.
// The state of the msg and object are undefined if false is returned.
// The network packets contain userIds, and Read/Write will translate from userId to a
// sessionUserIndex. The sessionUserIndex should be the same on all peers, but the
// userId has to be used in case the target player quits while the message is on the
// wire from the originating peer to the server.
bool Read( idLobby * lobby, idBitMsg & msg );
bool Write( idLobby * lobby, idBitMsg & msg );
};
int GetTotalOutgoingRate(); // returns total instant outgoing bandwidth in B/s
//private:
public: // Turning this on for now, for the sake of getting this up and running to see where things are
// State functions
void State_Idle();
void State_Create_Lobby_Backend();
void State_Searching();
void State_Obtaining_Address();
void State_Finalize_Connect();
void State_Connect_Hello_Wait();
void SetState( lobbyState_t newState );
void StartCreating();
int FindPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID, bool ignoreSessionID = false );
int FindAnyPeer( const lobbyAddress_t & remoteAddress ) const;
int FindFreePeer() const;
int AddPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID );
void DisconnectPeerFromSession( int p );
void SetPeerConnectionState( int p, connectionState_t newState, bool skipGoodbye = false );
void DisconnectAllPeers();
virtual void SendReliable( int type, idBitMsg & msg, bool callReceiveReliable = true, peerMask_t sessionUserMask = MAX_UNSIGNED_TYPE( peerMask_t ) );
virtual void SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg & msg );
virtual void SendReliableToHost( int type, idBitMsg & msg );
void SendGoodbye( const lobbyAddress_t & remoteAddress, bool wasFull = false );
void QueueReliableMessage( int peerNum, byte type ) { QueueReliableMessage( peerNum, type, NULL, 0 ); }
void QueueReliableMessage( int p, byte type, const byte * data, int dataLen );
virtual int GetNumConnectedPeers() const;
virtual int GetNumConnectedPeersInGame() const;
void SendMatchParmsToPeers();
static bool IsReliablePlayerToPlayerType( byte type );
void HandleReliablePlayerToPlayerMsg( int peerNum, idBitMsg & msg, int type );
void HandleReliablePlayerToPlayerMsg( const reliablePlayerToPlayerHeader_t & info, idBitMsg & msg, int reliableType );
void SendConnectionLess( const lobbyAddress_t & remoteAddress, byte type ) { SendConnectionLess( remoteAddress, type, NULL, 0 ); }
void SendConnectionLess( const lobbyAddress_t & remoteAddress, byte type, const byte * data, int dataLen );
void SendConnectionRequest();
void ConnectTo( const lobbyConnectInfo_t & connectInfo, bool fromInvite );
void HandleGoodbyeFromPeer( int peerNum, lobbyAddress_t & remoteAddress, int msgType );
void HandleConnectionAttemptFailed();
bool ConnectToNextSearchResult();
bool CheckVersion( idBitMsg & msg, lobbyAddress_t peerAddress );
bool VerifyNumConnectingUsers( idBitMsg & msg );
bool VerifyLobbyUserIDs( idBitMsg & msg );
int HandleInitialPeerConnection( idBitMsg & msg, const lobbyAddress_t & peerAddress, int peerNum );
void InitStateLobbyHost();
void SendMembersToLobby( lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers );
void SendMembersToLobby( idLobby & destLobby, bool waitForOtherMembers );
void SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers );
void SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, bool waitForOtherMembers );
void NotifyPartyOfLeavingGameLobby();
uint32 GetPartyTokenAsHost();
virtual void DrawDebugNetworkHUD() const;
virtual void DrawDebugNetworkHUD2() const;
virtual void DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw );
void CheckHeartBeats();
bool IsLosingConnectionToHost() const;
bool IsMigratedStatsGame() const;
bool ShouldRelaunchMigrationGame() const;
bool ShouldShowMigratingDialog() const;
bool IsMigrating() const;
// Pings
struct pktPing_t {
int timestamp;
};
void PingPeers();
void SendPingValues();
void PumpPings();
void HandleReliablePing( int p, idBitMsg & msg );
void HandlePingReply( int p, const pktPing_t & ping );
void HandlePingValues( idBitMsg & msg );
void HandleBandwidhTestValue( int p, idBitMsg & msg );
void HandleMigrationGameData( idBitMsg & msg );
void HandleHeadsetStateChange( int fromPeer, idBitMsg & msg );
bool SendAnotherFragment( int p );
bool CanSendMoreData( int p );
void ProcessOutgoingMsg( int p, const void * data, int size, bool isOOB, int userData );
void ResendReliables( int p );
void PumpPackets();
void UpdateMatchParms( const idMatchParameters & p );
// SessionID helpers
idPacketProcessor::sessionId_t EncodeSessionID( uint32 key ) const;
void DecodeSessionID( idPacketProcessor::sessionId_t sessionID, uint32 & key ) const;
idPacketProcessor::sessionId_t GenerateSessionID() const;
bool SessionIDCanBeUsedForInBand( idPacketProcessor::sessionId_t sessionID ) const;
idPacketProcessor::sessionId_t IncrementSessionID( idPacketProcessor::sessionId_t sessionID ) const;
void HandleHelloAck( int p, idBitMsg & msg );
virtual const char * GetLobbyUserName( lobbyUserID_t lobbyUserID ) const;
virtual bool GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const;
virtual bool GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const;
virtual int GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const;
virtual int GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const;
virtual int GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const;
virtual int GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const;
virtual bool SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber );
virtual int GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const;
virtual idPlayerProfile * GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID );
virtual idLocalUser * GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID );
virtual int GetNumLobbyUsersOnTeam( int teamNumber ) const;
const char * GetPeerName( int peerNum ) const;
virtual const char * GetHostUserName() const;
void HandleReliableMsg( int p, idBitMsg & msg );
// Bandwidth / Qos / Throttling
void BeginBandwidthTest();
bool BandwidthTestStarted();
void ServerUpdateBandwidthTest();
void ClientUpdateBandwidthTest();
void ThrottlePeerSnapRate( int peerNum );
//
// sys_session_instance_users.cpp
//
lobbyUser_t * AllocUser( const lobbyUser_t & defaults );
void FreeUser( lobbyUser_t * user );
bool VerifyUser( const lobbyUser_t * lobbyUser ) const;
void FreeAllUsers();
void RegisterUser( lobbyUser_t * lobbyUser );
void UnregisterUser( lobbyUser_t * lobbyUser );
bool IsSessionUserLocal( const lobbyUser_t * lobbyUser ) const;
bool IsSessionUserIndexLocal( int i ) const;
int GetLobbyUserIndexByID( lobbyUserID_t lobbyUserId, bool ignoreLobbyType = false ) const;
lobbyUser_t * GetLobbyUserByID( lobbyUserID_t lobbyUserId, bool ignoreLobbyType = false );
// Helper function to create a lobby user from a local user
lobbyUser_t CreateLobbyUserFromLocalUser( const idLocalUser * localUser );
// This function is designed to initialize the session users of type lobbyType (TYPE_GAME or TYPE_PARTY)
// to the current list of local users that are being tracked by the sign-in manager
void InitSessionUsersFromLocalUsers( bool onlineMatch );
// Convert an local userhandle to a session user (-1 if there is no session user with this handle)
int GetLobbyUserIndexByLocalUserHandle( const localUserHandle_t localUserHandle ) const;
// This takes a session user, and converts to a controller user
idLocalUser * GetLocalUserFromLobbyUserIndex( int lobbyUserIndex );
// Takes a controller user, and converts to a session user (will return NULL if there is no session user for this controller user)
lobbyUser_t * GetSessionUserFromLocalUser( const idLocalUser * controller );
void RemoveUsersWithDisconnectedPeers();
void RemoveSessionUsersByIDList( idList< lobbyUserID_t > & usersToRemoveByID );
void SendNewUsersToPeers( int skipPeer, int userStart, int numUsers );
void SendPeersMicStatusToNewUsers( int peerNumber );
void AddUsersFromMsg( idBitMsg & msg, int fromPeer );
void UpdateSessionUserOnPeers( idBitMsg & msg );
void HandleUpdateSessionUser( idBitMsg & msg );
void CreateUserUpdateMessage( int userIndex, idBitMsg & msg );
void UpdateLocalSessionUsers();
int PeerIndexForSessionUserIndex( int sessionUserIndex ) const;
void HandleUserConnectFailure( int p, idBitMsg & inMsg, int reliableType );
void ProcessUserDisconnectMsg( idBitMsg & msg );
void CompactDisconnectedUsers();
// Sends a request to the host to join a local user to a session
void RequestLocalUserJoin( idLocalUser * localUser );
// Sends a request to the host to remove a session user from the session
void RequestSessionUserDisconnect( int sessionUserIndex );
// This function sycs the session users with the current list of of local users on the signin manager.
// It will remove the session users that are either no longer on the signin manager, or it
// will remove them if they are no longer allowed to be in the session.
// If it finds a local users that are not in a particular session, it will add that user if allowed.
void SyncLobbyUsersWithLocalUsers( bool allowJoin, bool onlineMatch );
bool ValidateConnectedUser( const lobbyUser_t * user ) const;
virtual bool IsLobbyUserDisconnected( int userIndex ) const;
virtual bool IsLobbyUserValid( lobbyUserID_t lobbyUserID ) const;
virtual bool IsLobbyUserLoaded( lobbyUserID_t lobbyUserID ) const;
virtual bool LobbyUserHasFirstFullSnap( lobbyUserID_t lobbyUserID ) const;
virtual lobbyUserID_t GetLobbyUserIdByOrdinal( int userIndex ) const;
virtual int GetLobbyUserIndexFromLobbyUserID( lobbyUserID_t lobbyUserID ) const;
virtual void EnableSnapshotsForLobbyUser( lobbyUserID_t lobbyUserID );
virtual bool IsPeerDisconnected( int peerIndex ) const { return !peers[peerIndex].IsConnected(); }
float GetAverageSessionLevel();
float GetAverageLocalUserLevel( bool onlineOnly );
void QueueReliablePlayerToPlayerMessage( int fromSessionUserIndex, int toSessionUserIndex, reliablePlayerToPlayer_t type, const byte * data, int dataLen );
virtual void KickLobbyUser( lobbyUserID_t lobbyUserID );
int GetNumConnectedUsers() const;
//
// sys_session_instance_migrate.cpp
//
bool IsBetterHost( int ping1, lobbyUserID_t userId1, int ping2, lobbyUserID_t userId2 );
int FindMigrationInviteIndex( lobbyAddress_t & address );
void UpdateHostMigration();
void BuildMigrationInviteList( bool inviteOldHost );
void PickNewHost( bool forceMe = false, bool inviteOldHost = false );
void PickNewHostInternal( bool forceMe, bool inviteOldHost );
void BecomeHost();
void EndMigration();
void ResetAllMigrationState();
void SendMigrationGameData();
bool GetMigrationGameData( idBitMsg &msg, bool reading );
bool GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg &msg, bool reading );
//
// Snapshots
// sys_session_instance_snapshot.cpp
//
void UpdateSnaps();
bool SendCompletedSnaps();
bool SendResources( int p );
bool SubmitPendingSnap( int p );
void SendCompletedPendingSnap( int p );
void CheckPeerThrottle( int p );
void ApplySnapshotDelta( int p, int snapshotNumber );
bool ApplySnapshotDeltaInternal( int p, int snapshotNumber );
void SendSnapshotToPeer( idSnapShot & ss, int p );
bool AllPeersHaveBaseState();
void ThrottleSnapsForXSeconds( int p, int seconds, bool recoverPing );
bool FirstSnapHasBeenSent( int p );
virtual bool EnsureAllPeersHaveBaseState();
virtual bool AllPeersHaveStaleSnapObj( int objId );
virtual bool AllPeersHaveExpectedSnapObj( int objId );
virtual void MarkSnapObjDeleted( int objId );
virtual void RefreshSnapObj( int objId );
void ResetBandwidthStats();
void DetectSaturation( int p );
virtual void AddSnapObjTemplate( int objID, idBitMsg & msg );
static const int MAX_PEERS = MAX_PLAYERS;
//------------------------
// Pings
//------------------------
struct pktPingValues_t {
idArray<short, MAX_PEERS> pings;
};
static const int PING_INTERVAL_MS = 3000;
int lastPingValuesRecvTime; // so clients can display something when server stops pinging
int nextSendPingValuesTime; // the next time to send RELIABLE_PING_VALUES
static const int MIGRATION_GAME_DATA_INTERVAL_MS = 1000;
int nextSendMigrationGameTime; // when to send next migration game data
int nextSendMigrationGamePeer; // who to send next migration game data to
lobbyType_t lobbyType;
lobbyState_t state; // State of this lobby
failedReason_t failedReason;
int host; // which peer is the host of this type of session (-1 if we are the host)
int peerIndexOnHost; // -1 if we are the host
lobbyAddress_t hostAddress; // address of the host for this type of session
bool isHost; // true if we are the host
idLobbyBackend * lobbyBackend;
int helloStartTime; // Used to determine when the first hello was sent
int lastConnectRequest; // Used to determine when the last hello was sent
int connectionAttempts; // Number of connection attempts
bool needToDisplayMigrateMsg; // If true, we migrated as host, so we need to display the msg as soon as the lobby is active
gameDialogMessages_t migrationDlg; // current migration dialog we should be showing
uint8 migrateMsgFlags; // cached match flags from the old game we migrated from, so we know what type of msg to display
bool joiningMigratedGame; // we are joining a migrated game and need to tell the session mgr if we succeed or fail
// ------------------------
// Bandwidth challenge
// ------------------------
int bandwidthChallengeEndTime; // When the challenge will end/timeout
int bandwidthChallengeStartTime; // time in MS the challenge started
bool bandwidthChallengeFinished; // (HOST) test is finished and we received results back from all peers (or timed out)
int bandwidthChallengeNumGoodSeq; // (PEER) num of good, in order packets we recevieved
int lastSnapBspHistoryUpdateSequence;
void SaveDisconnectedUser( const lobbyUser_t & user ); // This is needed to get the a user's gamertag after disconnection.
idSessionCallbacks * sessionCB;
enum migrationState_t {
MIGRATE_NONE,
MIGRATE_PICKING_HOST,
MIGRATE_BECOMING_HOST,
};
struct migrationInvite_t {
migrationInvite_t() {
lastInviteTime = -1;
pingMs = 0;
migrationGameData = -1;
}
lobbyAddress_t address;
int pingMs;
lobbyUserID_t userId;
int lastInviteTime;
int migrationGameData;
};
struct migrationInfo_t {
migrationInfo_t() {
state = MIGRATE_NONE;
ourPingMs = 0;
ourUserId = lobbyUserID_t();
}
migrationState_t state;
idStaticList< migrationInvite_t, MAX_PEERS > invites;
int migrationStartTime;
int ourPingMs;
lobbyUserID_t ourUserId;
struct persistUntilGameEnds_t {
persistUntilGameEnds_t() {
Clear();
}
void Clear() {
wasMigratedHost = false;
wasMigratedJoin = false;
wasMigratedGame = false;
ourGameData = -1;
hasGameData = false;
hasRelaunchedMigratedGame = false;
memset( gameData, 0, sizeof( gameData ) );
memset( gameDataUser, 0, sizeof( gameDataUser ) );
}
int ourGameData;
bool wasMigratedHost; // we are hosting a migrated session
bool wasMigratedJoin; // we joined a migrated session
bool wasMigratedGame; // If true, we migrated from a game
bool hasRelaunchedMigratedGame;
// A generic blob of data that the gamechallenge (or anything else) can read and write to for host migration
static const int MIGRATION_GAME_DATA_SIZE = 32;
byte gameData[ MIGRATION_GAME_DATA_SIZE ];
static const int MIGRATION_GAME_DATA_USER_SIZE = 64;
byte gameDataUser[ MAX_PLAYERS ][ MIGRATION_GAME_DATA_USER_SIZE ];
bool hasGameData;
} persistUntilGameEndsData;
};
struct disconnectedUser_t {
lobbyUserID_t lobbyUserID; // Locally generated to be unique, and internally keeps the local user handle
char gamertag[lobbyUser_t::MAX_GAMERTAG];
};
migrationInfo_t migrationInfo;
bool showHostLeftTheSession;
bool connectIsFromInvite;
idList< lobbyConnectInfo_t > searchResults;
typedef idStaticList< lobbyUser_t *, MAX_PLAYERS > idLobbyUserList;
typedef idStaticList< lobbyUser_t, MAX_PLAYERS > idLobbyUserPool;
idLobbyUserList userList; // list of currently connected users to this lobby
idLobbyUserList freeUsers; // list of free users
idLobbyUserPool userPool;
idList< disconnectedUser_t> disconnectedUsers; // List of users which were connected, but aren't anymore, for printing their name on the hud
idStaticList< peer_t, MAX_PEERS > peers; // Unique machines connected to this lobby
uint32 partyToken;
idMatchParameters parms;
bool loaded; // Used for game sessions, whether this machine is loaded or not
bool respondToArbitrate; // true when the host has requested us to arbitrate our session (for TYPE_GAME only)
bool everyoneArbitrated;
bool waitForPartyOk;
bool startLoadingFromHost;
//------------------------
// Snapshot jobs
//------------------------
static const int SNAP_OBJ_JOB_MEMORY = 1024 * 128; // 128k of obj memory
lzwCompressionData_t * lzwData; // Shared across all snapshot jobs
uint8 * objMemory; // Shared across all snapshot jobs
bool haveSubmittedSnaps; // True if we previously submitted snaps to jobs
idSnapShot * localReadSS;
struct snapDeltaAck_t {
int p;
int snapshotNumber;
};
idStaticList< snapDeltaAck_t, 16 > snapDeltaAckQueue;
};
/*
========================
idSessionCallbacks
========================
*/
class idSessionCallbacks {
public:
virtual idLobby & GetPartyLobby() = 0;
virtual idLobby & GetGameLobby() = 0;
virtual idLobby & GetActingGameStateLobby() = 0;
virtual idLobby * GetLobbyFromType( idLobby::lobbyType_t lobbyType ) = 0;
virtual int GetUniquePlayerId() const = 0;
virtual idSignInManagerBase & GetSignInManager() = 0;
virtual void SendRawPacket( const lobbyAddress_t & to, const void * data, int size, bool useDirectPort ) = 0;
virtual bool BecomingHost( idLobby & lobby ) = 0; // Called when a lobby is about to become host
virtual void BecameHost( idLobby & lobby ) = 0; // Called when a lobby becomes a host
virtual bool BecomingPeer( idLobby & lobby ) = 0; // Called when a lobby is about to become peer
virtual void BecamePeer( idLobby & lobby ) = 0; // Called when a lobby becomes a peer
virtual void FailedGameMigration( idLobby & lobby ) = 0;
virtual void MigrationEnded( idLobby & lobby ) = 0;
virtual void GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ) = 0;
virtual uint32 GetSessionOptions() = 0;
virtual bool AnyPeerHasAddress( const lobbyAddress_t & remoteAddress ) const = 0;
virtual idSession::sessionState_t GetState() const = 0;
virtual void ClearMigrationState() = 0;
// Called when the lobby receives a RELIABLE_ENDMATCH msg
virtual void EndMatchInternal( bool premature=false ) = 0;
// Called when the game lobby receives leaderboard stats
virtual void RecvLeaderboardStats( idBitMsg & msg ) = 0;
// Called once the lobby received its first full snap (used to advance from LOADING to INGAME state)
virtual void ReceivedFullSnap() = 0;
// Called when lobby received RELIABLE_PARTY_LEAVE_GAME_LOBBY msg
virtual void LeaveGameLobby() = 0;
virtual void PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ) = 0;
virtual bool PreMigrateInvite( idLobby & lobby ) = 0;
virtual void HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg ) = 0;
// ConnectAndMoveToLobby is called when the lobby receives a RELIABLE_CONNECT_AND_MOVE_TO_LOBBY
virtual void ConnectAndMoveToLobby( idLobby::lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForPartyOk ) = 0;
virtual class idVoiceChatMgr * GetVoiceChat() = 0;
virtual void HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) = 0;
virtual void HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) = 0;
virtual void HandlePeerMatchParamUpdate( int peer, int msg ) = 0;
virtual idLobbyBackend * CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) = 0;
virtual idLobbyBackend * FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) = 0;
virtual idLobbyBackend * JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo , idLobbyBackend::lobbyBackendType_t lobbyType ) = 0;
virtual void DestroyLobbyBackend( idLobbyBackend * lobbyBackend ) = 0;
};
+280
View File
@@ -0,0 +1,280 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_LOBBY_BACKEND_H__
#define __SYS_LOBBY_BACKEND_H__
extern idCVar net_verboseResource;
#define NET_VERBOSERESOURCE_PRINT if ( net_verboseResource.GetBool() ) idLib::Printf
extern idCVar net_verbose;
#define NET_VERBOSE_PRINT if ( net_verbose.GetBool() ) idLib::Printf
class lobbyAddress_t {
public:
lobbyAddress_t();
void InitFromNetadr( const netadr_t & netadr );
void InitFromIPandPort( const char * ip, int port );
const char * ToString() const;
bool UsingRelay() const;
bool Compare( const lobbyAddress_t & addr, bool ignoreSessionCheck = false ) const;
void WriteToMsg( idBitMsg & msg ) const;
void ReadFromMsg( idBitMsg & msg );
// IP address
netadr_t netAddr;
};
struct lobbyConnectInfo_t {
public:
void WriteToMsg( idBitMsg & msg ) const {
msg.WriteNetadr( netAddr );
}
void ReadFromMsg( idBitMsg & msg ) {
msg.ReadNetadr( &netAddr );
}
lobbyConnectInfo_t() : netAddr() { }
netadr_t netAddr;
};
class idNetSessionPort {
public:
idNetSessionPort();
bool InitPort( int portNumber, bool useBackend );
bool ReadRawPacket( lobbyAddress_t & from, void * data, int & size, int maxSize );
void SendRawPacket( const lobbyAddress_t & to, const void * data, int size );
bool IsOpen();
void Close();
private:
float forcePacketDropCurr; // Used with net_forceDrop and net_forceDropCorrelation
float forcePacketDropPrev;
idUDP UDP;
};
struct lobbyUser_t {
static const int INVALID_PING = 9999;
// gamertags can be up to 16 4-byte characters + \0
static const int MAX_GAMERTAG = 64 + 1;
lobbyUser_t() {
isBot = false;
peerIndex = -1;
disconnecting = false;
level = 1;
pingMs = INVALID_PING;
teamNumber = 0;
arbitrationAcked = false;
partyToken = 0;
selectedSkin = 0;
weaponAutoSwitch = true;
weaponAutoReload = true;
migrationGameData = -1;
}
// Common variables
bool isBot; // true if lobbyUser is a bot.
int peerIndex; // peer number on host
lobbyUserID_t lobbyUserID; // Locally generated to be unique, and internally keeps the local user handle
char gamertag[MAX_GAMERTAG];
int pingMs; // round trip time in milliseconds
bool disconnecting; // true if we've sent a msg to disconnect this user from the session
int level;
int teamNumber;
uint32 partyToken; // set by the server when people join as a party
int selectedSkin;
bool weaponAutoSwitch;
bool weaponAutoReload;
bool arbitrationAcked; // if the user is verified for arbitration
lobbyAddress_t address;
int migrationGameData; // index into the local migration gamedata array that is associated with this user. -1=no migration game data available
// Platform variables
bool IsDisconnected() const { return lobbyUserID.IsValid() ? false : true; }
void WriteToMsg( idBitMsg & msg ) {
address.WriteToMsg( msg );
lobbyUserID.WriteToMsg( msg );
msg.WriteLong( peerIndex );
msg.WriteShort( pingMs );
msg.WriteLong( partyToken );
msg.WriteString( gamertag, MAX_GAMERTAG, false );
WriteClientMutableData( msg );
}
void ReadFromMsg( idBitMsg & msg ) {
address.ReadFromMsg( msg );
lobbyUserID.ReadFromMsg( msg );
peerIndex = msg.ReadLong();
pingMs = msg.ReadShort();
partyToken = msg.ReadLong();
msg.ReadString( gamertag, MAX_GAMERTAG );
ReadClientMutableData( msg );
}
bool UpdateClientMutableData( const idLocalUser * localUser );
void WriteClientMutableData( idBitMsg & msg ) {
msg.WriteBits( selectedSkin, 4 );
msg.WriteBits( teamNumber, 2 ); // We need two bits since we use team value of 2 for spectating
msg.WriteBool( weaponAutoSwitch );
msg.WriteBool( weaponAutoReload );
release_assert( msg.GetWriteBit() == 0 );
}
void ReadClientMutableData( idBitMsg & msg ) {
selectedSkin = msg.ReadBits( 4 );
teamNumber = msg.ReadBits( 2 ); // We need two bits since we use team value of 2 for spectating
weaponAutoSwitch = msg.ReadBool();
weaponAutoReload = msg.ReadBool();
}
};
/*
================================================
idLobbyBackend
This class interfaces with the various back ends for the different platforms
================================================
*/
class idLobbyBackend {
public:
enum lobbyBackendState_t {
STATE_INVALID = 0,
STATE_READY = 1,
STATE_CREATING = 2, // In the process of creating the lobby as a host
STATE_SEARCHING = 3, // In the process of searching for a lobby to join
STATE_OBTAINING_ADDRESS = 4, // In the process of obtaining the address of the lobby owner
STATE_ARBITRATING = 5, // Arbitrating
STATE_SHUTTING_DOWN = 6, // In the process of shutting down
STATE_SHUTDOWN = 7, // Was a host or peer at one point, now ready to be deleted
STATE_FAILED = 8, // Failure occurred
NUM_STATES
};
static const char * GetStateString( lobbyBackendState_t state_ ) {
static const char * stateToString[NUM_STATES] = {
"STATE_INVALID",
"STATE_READY",
"STATE_CREATING",
"STATE_SEARCHING",
"STATE_OBTAINING_ADDRESS",
"STATE_ARBITRATING",
"STATE_SHUTTING_DOWN",
"STATE_SHUTDOWN",
"STATE_FAILED"
};
return stateToString[ state_ ];
}
enum lobbyBackendType_t {
TYPE_PARTY = 0,
TYPE_GAME = 1,
TYPE_GAME_STATE = 2,
TYPE_INVALID = 0xff,
};
idLobbyBackend() : type( TYPE_INVALID ), isHost( false ), isLocal( false ) {}
idLobbyBackend( lobbyBackendType_t lobbyType ) : type( lobbyType ), isHost( false ), isLocal( false ) {}
virtual void StartHosting( const idMatchParameters & p, float skillLevel, lobbyBackendType_t type ) = 0;
virtual void StartFinding( const idMatchParameters & p, int numPartyUsers, float skillLevel ) = 0;
virtual void JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo ) = 0;
virtual void GetSearchResults( idList< lobbyConnectInfo_t > & searchResults ) = 0;
virtual lobbyConnectInfo_t GetConnectInfo() = 0;
virtual void FillMsgWithPostConnectInfo( idBitMsg & msg ) = 0; // Passed itno PostConnectFromMsg
virtual void PostConnectFromMsg( idBitMsg & msg ) = 0; // Uses results from FillMsgWithPostConnectInfo
virtual bool IsOwnerOfConnectInfo( const lobbyConnectInfo_t & connectInfo ) const { return false; }
virtual void Shutdown() = 0;
virtual void GetOwnerAddress( lobbyAddress_t & outAddr ) = 0;
virtual bool IsHost() { return isHost; }
virtual void SetIsJoinable( bool joinable ) {}
virtual void Pump() = 0;
virtual void UpdateMatchParms( const idMatchParameters & p ) = 0;
virtual void UpdateLobbySkill( float lobbySkill ) = 0;
virtual void SetInGame( bool value ) {}
virtual lobbyBackendState_t GetState() = 0;
virtual bool IsLocal() const { return isLocal; }
virtual bool IsOnline() const { return !isLocal; }
virtual bool StartArbitration() { return false; }
virtual void Arbitrate() {}
virtual void VerifyArbitration() {}
virtual bool UserArbitrated( lobbyUser_t * user ) { return false; }
virtual void RegisterUser( lobbyUser_t * user, bool isLocal ) {}
virtual void UnregisterUser( lobbyUser_t * user, bool isLocal ) {}
virtual void StartSession() {}
virtual void EndSession() {}
virtual bool IsSessionStarted() { return false; }
virtual void FlushStats() {}
virtual void BecomeHost( int numInvites ) {} // Become the host of this lobby
virtual void RegisterAddress( lobbyAddress_t & address ) {} // Called after becoming a new host, to register old addresses to send invites to
virtual void FinishBecomeHost() {}
void SetLobbyType( lobbyBackendType_t lobbyType ) { type = lobbyType; }
lobbyBackendType_t GetLobbyType() const { return type; }
const char * GetLobbyTypeString() const { return ( GetLobbyType() == TYPE_PARTY ) ? "Party" : "Game"; }
bool IsRanked() { return MatchTypeIsRanked( parms.matchFlags ); }
bool IsPrivate() { return MatchTypeIsPrivate( parms.matchFlags ); }
protected:
lobbyBackendType_t type;
idMatchParameters parms;
bool isLocal; // True if this lobby is restricted to local play only (won't need and can't connect to online lobbies)
bool isHost; // True if we created this lobby
};
class idLobbyToSessionCB {
public:
virtual class idLobbyBackend * GetLobbyBackend( idLobbyBackend::lobbyBackendType_t type ) const = 0;
virtual bool CanJoinLocalHost() const = 0;
// Ugh, hate having to ifdef these, but we're doing some fairly platform specific callbacks
};
#endif // __SYS_LOBBY_BACKEND_H__
+226
View File
@@ -0,0 +1,226 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "sys_lobby_backend.h"
#include "sys_lobby_backend_direct.h"
extern idCVar net_port;
extern idLobbyToSessionCB * lobbyToSessionCB;
/*
========================
idLobbyBackendDirect::idLobbyBackendWin
========================
*/
idLobbyBackendDirect::idLobbyBackendDirect() {
state = STATE_INVALID;
}
/*
========================
idLobbyBackendDirect::StartHosting
========================
*/
void idLobbyBackendDirect::StartHosting( const idMatchParameters & p, float skillLevel, lobbyBackendType_t type ) {
NET_VERBOSE_PRINT( "idLobbyBackendDirect::StartHosting\n" );
isLocal = MatchTypeIsLocal( p.matchFlags );
isHost = true;
state = STATE_READY;
isLocal = true;
}
/*
========================
idLobbyBackendDirect::StartFinding
========================
*/
void idLobbyBackendDirect::StartFinding( const idMatchParameters & p, int numPartyUsers, float skillLevel ) {
isLocal = MatchTypeIsLocal( p.matchFlags );
isHost = false;
if ( lobbyToSessionCB->CanJoinLocalHost() ) {
state = STATE_READY;
} else {
state = STATE_FAILED;
}
}
/*
========================
idLobbyBackendDirect::GetSearchResults
========================
*/
void idLobbyBackendDirect::GetSearchResults( idList< lobbyConnectInfo_t > & searchResults ) {
lobbyConnectInfo_t fakeResult;
searchResults.Clear();
searchResults.Append( fakeResult );
}
/*
========================
idLobbyBackendDirect::JoinFromConnectInfo
========================
*/
void idLobbyBackendDirect::JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo ) {
if ( lobbyToSessionCB->CanJoinLocalHost() ) {
Sys_StringToNetAdr( "localhost", &address, true );
address.port = net_port.GetInteger();
} else {
address = connectInfo.netAddr;
}
state = STATE_READY;
isLocal = false;
isHost = false;
}
/*
========================
idLobbyBackendDirect::Shutdown
========================
*/
void idLobbyBackendDirect::Shutdown() {
state = STATE_SHUTDOWN;
}
/*
========================
idLobbyBackendDirect::BecomeHost
========================
*/
void idLobbyBackendDirect::BecomeHost( int numInvites ) {
}
/*
========================
idLobbyBackendDirect::FinishBecomeHost
========================
*/
void idLobbyBackendDirect::FinishBecomeHost() {
isHost = true;
}
/*
========================
idLobbyBackendDirect::GetOwnerAddress
========================
*/
void idLobbyBackendDirect::GetOwnerAddress( lobbyAddress_t & outAddr ) {
outAddr.netAddr = address;
state = STATE_READY;
}
/*
========================
idLobbyBackendDirect::SetIsJoinable
========================
*/
void idLobbyBackendDirect::SetIsJoinable( bool joinable ) {
}
/*
========================
idLobbyBackendDirect::GetConnectInfo
========================
*/
lobbyConnectInfo_t idLobbyBackendDirect::GetConnectInfo() {
lobbyConnectInfo_t connectInfo;
// If we aren't the host, this lobby should have been joined through JoinFromConnectInfo
if ( IsHost() ) {
// If we are the host, give them our ip address
const char * ip = Sys_GetLocalIP( 0 );
Sys_StringToNetAdr( ip, &address, false );
address.port = net_port.GetInteger();
}
connectInfo.netAddr = address;
return connectInfo;
}
/*
========================
idLobbyBackendDirect::IsOwnerOfConnectInfo
========================
*/
bool idLobbyBackendDirect::IsOwnerOfConnectInfo( const lobbyConnectInfo_t & connectInfo ) const {
return Sys_CompareNetAdrBase( address, connectInfo.netAddr );
}
/*
========================
idLobbyBackendDirect::Pump
========================
*/
void idLobbyBackendDirect::Pump() {
}
/*
========================
idLobbyBackendDirect::UpdateMatchParms
========================
*/
void idLobbyBackendDirect::UpdateMatchParms( const idMatchParameters & p ) {
}
/*
========================
idLobbyBackendDirect::UpdateLobbySkill
========================
*/
void idLobbyBackendDirect::UpdateLobbySkill( float lobbySkill ) {
}
/*
========================
idLobbyBackendDirect::SetInGame
========================
*/
void idLobbyBackendDirect::SetInGame( bool value ) {
}
/*
========================
idLobbyBackendDirect::RegisterUser
========================
*/
void idLobbyBackendDirect::RegisterUser( lobbyUser_t * user, bool isLocal ) {
}
/*
========================
idLobbyBackendDirect::UnregisterUser
========================
*/
void idLobbyBackendDirect::UnregisterUser( lobbyUser_t * user, bool isLocal ) {
}
+70
View File
@@ -0,0 +1,70 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_LOBBY_BACKEND_DIRECT_H__
#define __SYS_LOBBY_BACKEND_DIRECT_H__
/*
========================
idLobbyBackendDirect
========================
*/
class idLobbyBackendDirect : public idLobbyBackend {
public:
idLobbyBackendDirect();
// idLobbyBackend interface
virtual void StartHosting( const idMatchParameters & p, float skillLevel, lobbyBackendType_t type );
virtual void StartFinding( const idMatchParameters & p, int numPartyUsers, float skillLevel );
virtual void JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo );
virtual void GetSearchResults( idList< lobbyConnectInfo_t > & searchResults );
virtual void FillMsgWithPostConnectInfo( idBitMsg & msg ) {}
virtual void PostConnectFromMsg( idBitMsg & msg ) {}
virtual void Shutdown();
virtual void GetOwnerAddress( lobbyAddress_t & outAddr );
virtual void SetIsJoinable( bool joinable );
virtual lobbyConnectInfo_t GetConnectInfo();
virtual bool IsOwnerOfConnectInfo( const lobbyConnectInfo_t & connectInfo ) const;
virtual void Pump();
virtual void UpdateMatchParms( const idMatchParameters & p );
virtual void UpdateLobbySkill( float lobbySkill );
virtual void SetInGame( bool value );
virtual lobbyBackendState_t GetState() { return state; }
virtual void BecomeHost( int numInvites );
virtual void FinishBecomeHost();
virtual void RegisterUser( lobbyUser_t * user, bool isLocal );
virtual void UnregisterUser( lobbyUser_t * user, bool isLocal );
private:
lobbyBackendState_t state;
netadr_t address;
};
#endif // __SYS_LOBBY_BACKEND_DIRECT_H__
+546
View File
@@ -0,0 +1,546 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "sys_lobby.h"
idCVar net_migration_debug( "net_migration_debug", "0", CVAR_BOOL, "debug" );
idCVar net_migration_disable( "net_migration_disable", "0", CVAR_BOOL, "debug" );
idCVar net_migration_forcePeerAsHost( "net_migration_forcePeerAsHost", "-1", CVAR_INTEGER, "When set to >-1, it forces that peer number to be the new host during migration" );
/*
========================
idLobby::IsBetterHost
========================
*/
bool idLobby::IsBetterHost( int ping1, lobbyUserID_t userId1, int ping2, lobbyUserID_t userId2 ) {
if ( lobbyType == TYPE_PARTY ) {
return userId1 < userId2; // Only use user id for party, since ping doesn't matter
}
if ( ping1 < ping2 ) {
// Better ping wins
return true;
} else if ( ping1 == ping2 && userId1 < userId2 ) {
// User id is tie breaker
return true;
}
return false;
}
/*
========================
idLobby::FindMigrationInviteIndex
========================
*/
int idLobby::FindMigrationInviteIndex( lobbyAddress_t & address ) {
if ( migrationInfo.state == MIGRATE_NONE ) {
return -1;
}
for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) {
if ( migrationInfo.invites[i].address.Compare( address, true ) ) {
return i;
}
}
return -1;
}
/*
========================
idLobby::UpdateHostMigration
========================
*/
void idLobby::UpdateHostMigration() {
int time = Sys_Milliseconds();
// If we are picking a new host, then update that
if ( migrationInfo.state == MIGRATE_PICKING_HOST ) {
const int MIGRATION_PICKING_HOST_TIMEOUT_IN_SECONDS = 20; // FIXME: set back to 5 // Give other hosts 5 seconds
if ( time - migrationInfo.migrationStartTime > session->GetTitleStorageInt( "MIGRATION_PICKING_HOST_TIMEOUT_IN_SECONDS", MIGRATION_PICKING_HOST_TIMEOUT_IN_SECONDS ) * 1000 ) {
// Just become the host if we haven't heard from a host in awhile
BecomeHost();
} else {
return;
}
}
// See if we are a new migrated host that needs to invite the original members back
if ( migrationInfo.state != MIGRATE_BECOMING_HOST ) {
return;
}
if ( lobbyBackend == NULL || lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
return;
}
if ( state != STATE_IDLE ) {
return;
}
if ( !IsHost() ) {
return;
}
const int MIGRATION_TIMEOUT_IN_SECONDS = 30; // FIXME: setting to 30 for dev purposes. 10 seems more reasonable. Need to make unloading game / loading lobby async
const int MIGRATION_INVITE_TIME_IN_SECONDS = 2;
if ( migrationInfo.invites.Num() == 0 || time - migrationInfo.migrationStartTime > session->GetTitleStorageInt( "MIGRATION_TIMEOUT_IN_SECONDS", MIGRATION_TIMEOUT_IN_SECONDS ) * 1000 ) {
// Either everyone acked, or we timed out, just keep who we have, and stop sending invites
EndMigration();
return;
}
// Send invites to anyone who hasn't responded
for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) {
if ( time - migrationInfo.invites[i].lastInviteTime < session->GetTitleStorageInt( "MIGRATION_INVITE_TIME_IN_SECONDS", MIGRATION_INVITE_TIME_IN_SECONDS ) * 1000 ) {
continue; // Not enough time passed
}
// Mark the time
migrationInfo.invites[i].lastInviteTime = time;
byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
idBitMsg outmsg( buffer, sizeof( buffer ) );
// Have lobbyBackend fill out msg with connection info
lobbyConnectInfo_t connectInfo = lobbyBackend->GetConnectInfo();
connectInfo.WriteToMsg( outmsg );
// Let them know whether or not this was from in game
outmsg.WriteBool( migrationInfo.persistUntilGameEndsData.wasMigratedGame );
NET_VERBOSE_PRINT( "NET: Sending migration invite to %s\n", migrationInfo.invites[i].address.ToString() );
// Send the migration invite
SendConnectionLess( migrationInfo.invites[i].address, OOB_MIGRATE_INVITE, outmsg.GetReadData(), outmsg.GetSize() );
}
}
/*
========================
idLobby::BuildMigrationInviteList
========================
*/
void idLobby::BuildMigrationInviteList( bool inviteOldHost ) {
migrationInfo.invites.Clear();
// Build a list of addresses we will send invites to (gather all unique remote addresses from the session user list)
for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
lobbyUser_t * user = GetLobbyUser( i );
if ( !verify( user != NULL ) ) {
continue;
}
if ( user->IsDisconnected() ) {
continue;
}
if ( IsSessionUserIndexLocal( i ) ) {
migrationInfo.ourPingMs = user->pingMs;
migrationInfo.ourUserId = user->lobbyUserID;
migrationInfo.persistUntilGameEndsData.ourGameData = user->migrationGameData;
NET_VERBOSE_PRINT( "^2NET: Migration game data for local user is index %d \n", user->migrationGameData );
continue; // Only interested in remote users
}
if ( !inviteOldHost && user->peerIndex == -1 ) {
continue; // Don't invite old host if told not to do so
}
if ( FindMigrationInviteIndex( user->address ) == -1 ) {
migrationInvite_t invite;
invite.address = user->address;
invite.pingMs = user->pingMs;
invite.userId = user->lobbyUserID;
invite.migrationGameData = user->migrationGameData;
invite.lastInviteTime = 0;
NET_VERBOSE_PRINT( "^2NET: Migration game data for user %s is index %d \n", user->gamertag, user->migrationGameData );
migrationInfo.invites.Append( invite );
}
}
}
/*
========================
idLobby::PickNewHost
========================
*/
void idLobby::PickNewHost( bool forceMe, bool inviteOldHost ) {
if ( IsHost() ) {
idLib::Printf( "PickNewHost: Already host of session %s\n", GetLobbyName() );
return;
}
sessionCB->PrePickNewHost( *this, forceMe, inviteOldHost );
}
/*
========================
idLobby::PickNewHostInternal
========================
*/
void idLobby::PickNewHostInternal( bool forceMe, bool inviteOldHost ) {
if ( migrationInfo.state == MIGRATE_PICKING_HOST ) {
return; // Already picking new host
}
idLib::Printf( "PickNewHost: Started picking new host %s.\n", GetLobbyName() );
if ( IsHost() ) {
idLib::Printf( "PickNewHost: Already host of session %s\n", GetLobbyName() );
return;
}
// Find the user with the lowest ping
int bestUserIndex = -1;
int bestPingMs = 0;
lobbyUserID_t bestUserId;
for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
lobbyUser_t * user = GetLobbyUser( i );
if ( !verify( user != NULL ) ) {
continue;
}
if ( user->IsDisconnected() ) {
continue;
}
if ( user->peerIndex == -1 ) {
continue; // Don't try and pick old host
}
if ( bestUserIndex == -1 || IsBetterHost( user->pingMs, user->lobbyUserID, bestPingMs, bestUserId ) ) {
bestUserIndex = i;
bestPingMs = user->pingMs;
bestUserId = user->lobbyUserID;
}
if ( user->peerIndex == net_migration_forcePeerAsHost.GetInteger() ) {
bestUserIndex = i;
bestPingMs = user->pingMs;
bestUserId = user->lobbyUserID;
break;
}
}
// Remember when we first started picking a new host
migrationInfo.state = MIGRATE_PICKING_HOST;
migrationInfo.migrationStartTime = Sys_Milliseconds();
migrationInfo.persistUntilGameEndsData.wasMigratedGame = sessionCB->GetState() == idSession::INGAME;
if ( bestUserIndex == -1 ) { // This can happen if we call PickNewHost on an lobby that was Shutdown
NET_VERBOSE_PRINT( "MIGRATION: PickNewHost was called on an lobby that was Shutdown\n" );
BecomeHost();
return;
}
NET_VERBOSE_PRINT( "MIGRATION: Chose user index %d (%s) for new host\n", bestUserIndex, GetLobbyUser( bestUserIndex )->gamertag );
bool bestWasLocal = IsSessionUserIndexLocal( bestUserIndex ); // Check before shutting down the lobby
migrateMsgFlags = parms.matchFlags; // Save off match parms
// Build invite list
BuildMigrationInviteList( inviteOldHost );
// If the best user is on this machine, then we become the host now, otherwise, wait for a new host to contact us
if ( forceMe || bestWasLocal ) {
BecomeHost();
}
}
/*
========================
idLobby::BecomeHost
========================
*/
void idLobby::BecomeHost() {
if ( !verify( migrationInfo.state == MIGRATE_PICKING_HOST ) ) {
idLib::Printf( "BecomeHost: Must be called from PickNewHost.\n" );
EndMigration();
return;
}
if ( IsHost() ) {
idLib::Printf( "BecomeHost: Already host of session.\n" );
EndMigration();
return;
}
if ( !sessionCB->BecomingHost( *this ) ) {
EndMigration();
return;
}
idLib::Printf( "BecomeHost: Sending %i invites on %s.\n", migrationInfo.invites.Num(), GetLobbyName() );
migrationInfo.state = MIGRATE_BECOMING_HOST;
migrationInfo.migrationStartTime = Sys_Milliseconds();
if ( lobbyBackend == NULL ) {
// If we don't have a lobbyBackend, then just create one
Shutdown();
StartCreating();
return;
}
// Shutdown the current lobby, but keep the lobbyBackend (we'll migrate it)
Shutdown( true );
// Migrate the lobbyBackend to host
lobbyBackend->BecomeHost( migrationInfo.invites.Num() );
// Wait for it to complete
SetState( STATE_CREATE_LOBBY_BACKEND );
}
/*
========================
idLobby::EndMigration
This gets called when we are done migrating, and invites will no longer be sent out.
========================
*/
void idLobby::EndMigration() {
if ( migrationInfo.state == MIGRATE_NONE ) {
idLib::Printf( "idSessionLocal::EndMigration: Not migrating.\n" );
return;
}
sessionCB->MigrationEnded( *this );
if ( lobbyBackend != NULL ) {
lobbyBackend->FinishBecomeHost();
}
migrationInfo.state = MIGRATE_NONE;
migrationInfo.invites.Clear();
}
/*
========================
idLobby::ResetAllMigrationState
This will reset all state related to host migration. Should be called
at match end so our next game is not treated as a migrated game
========================
*/
void idLobby::ResetAllMigrationState() {
migrationInfo.state = MIGRATE_NONE;
migrationInfo.invites.Clear();
migrationInfo.persistUntilGameEndsData.Clear();
migrateMsgFlags = 0;
common->Dialog().ClearDialog( GDM_MIGRATING );
common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
}
/*
========================
idLobby::GetMigrationGameData
This will setup the passed in idBitMsg to either read or write from the global migration game data buffer
========================
*/
bool idLobby::GetMigrationGameData( idBitMsg &msg, bool reading ) {
if ( reading ) {
if ( !IsMigratedStatsGame() || !migrationInfo.persistUntilGameEndsData.wasMigratedHost ) {
// This was not a migrated session, we have no migration data
return false;
}
msg.InitRead( migrationInfo.persistUntilGameEndsData.gameData, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) );
} else {
migrationInfo.persistUntilGameEndsData.hasGameData = true;
memset( migrationInfo.persistUntilGameEndsData.gameData, 0, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) );
msg.InitWrite( migrationInfo.persistUntilGameEndsData.gameData, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) );
}
return true;
}
/*
========================
idLobby::GetMigrationGameDataUser
This will setup the passed in idBitMsg to either read or write from the user's migration game data buffer
========================
*/
bool idLobby::GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg & msg, bool reading ) {
const int userNum = GetLobbyUserIndexByID( lobbyUserID );
if ( !verify( userNum >=0 && userNum < MAX_PLAYERS ) ) {
return false;
}
lobbyUser_t * u = GetLobbyUser( userNum );
if ( u != NULL ) {
if ( reading ) {
if ( !IsMigratedStatsGame() || !migrationInfo.persistUntilGameEndsData.wasMigratedHost ) {
// This was not a migrated session, we have no migration data
return false;
}
if ( u->migrationGameData >= 0 && u->migrationGameData < MAX_PLAYERS ) {
msg.InitRead( migrationInfo.persistUntilGameEndsData.gameDataUser[ u->migrationGameData ], sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[ 0 ] ) );
} else {
// We don't have migration data for this user
idLib::Warning( "No migration data for user %d in a migrated game (%d)", userNum, u->migrationGameData );
return false;
}
} else {
// Writing
migrationInfo.persistUntilGameEndsData.hasGameData = true;
u->migrationGameData = userNum;
memset( migrationInfo.persistUntilGameEndsData.gameDataUser[ userNum ], 0, sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[0] ) );
msg.InitWrite( migrationInfo.persistUntilGameEndsData.gameDataUser[ userNum ], sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[0] ) );
}
return true;
}
return false;
}
/*
========================
idLobby::HandleMigrationGameData
========================
*/
void idLobby::HandleMigrationGameData( idBitMsg & msg ) {
// Receives game migration data from the server. Just save off the raw data. If we ever become host we'll let the game code read
// that chunk in (we can't do anything with it now anyways: we don't have entities or any server code to read it in to)
migrationInfo.persistUntilGameEndsData.hasGameData = true;
// Reset each user's migration game data. If we don't receive new data for them in this msg, we don't want to use the old data
for ( int i=0; i < GetNumLobbyUsers(); i++ ) {
lobbyUser_t * u = GetLobbyUser( i );
if ( u != NULL ) {
u->migrationGameData = -1;
}
}
msg.ReadData( migrationInfo.persistUntilGameEndsData.gameData, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) );
int numUsers = msg.ReadByte();
int dataIndex=0;
for ( int i=0; i < numUsers; i++ ) {
lobbyUserID_t lobbyUserID;
lobbyUserID.ReadFromMsg( msg );
lobbyUser_t * user = GetLobbyUser( GetLobbyUserIndexByID( lobbyUserID ) );
if ( user != NULL ) {
NET_VERBOSE_PRINT( "NET: Got migration data[%d] for user %s\n", dataIndex, user->gamertag );
user->migrationGameData = dataIndex;
msg.ReadData( migrationInfo.persistUntilGameEndsData.gameDataUser[ dataIndex ], sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[ dataIndex ] ) );
dataIndex++;
}
}
}
/*
========================
idLobby::SendMigrationGameData
========================
*/
void idLobby::SendMigrationGameData() {
if ( net_migration_disable.GetBool() ) {
return;
}
if ( sessionCB->GetState() != idSession::INGAME ) {
return;
}
if ( !migrationInfo.persistUntilGameEndsData.hasGameData ) {
// Haven't been given any migration game data yet
return;
}
const int now = Sys_Milliseconds();
if ( nextSendMigrationGameTime > now ) {
return;
}
byte packetData[ idPacketProcessor::MAX_MSG_SIZE ];
idBitMsg msg( packetData, sizeof(packetData) );
// Write global data
msg.WriteData( &migrationInfo.persistUntilGameEndsData.gameData, sizeof( migrationInfo.persistUntilGameEndsData.gameData ) );
msg.WriteByte( GetNumLobbyUsers() );
// Write user data
for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) {
lobbyUser_t * u = GetLobbyUser( userIndex );
if ( u->IsDisconnected() || u->migrationGameData < 0 ) {
continue;
}
u->lobbyUserID.WriteToMsg( msg );
msg.WriteData( migrationInfo.persistUntilGameEndsData.gameDataUser[ u->migrationGameData ], sizeof( migrationInfo.persistUntilGameEndsData.gameDataUser[ u->migrationGameData ] ) );
}
// Send to 1 peer
for ( int i=0; i < peers.Num(); i++ ) {
int peerToSend = ( nextSendMigrationGamePeer + i ) % peers.Num();
if ( peers[ peerToSend ].IsConnected() && peers[ peerToSend ].loaded ) {
if ( peers[ peerToSend ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE / 2 ) {
// This is kind of a hack for development so we don't DC clients by sending them too many reliable migration messages
// when they aren't responding. Doesn't seem like a horrible thing to have in a shipping product but is not necessary.
NET_VERBOSE_PRINT("NET: Skipping reliable game migration data msg because client reliable queue is > half full\n");
} else {
if ( net_migration_debug.GetBool() ) {
idLib::Printf( "NET: Sending migration game data to peer %d. size: %d\n", peerToSend, msg.GetSize() );
}
QueueReliableMessage( peerToSend, RELIABLE_MIGRATION_GAME_DATA, msg.GetReadData(), msg.GetSize() );
}
break;
}
}
// Increment next send time / next send peer
nextSendMigrationGamePeer++;
if ( nextSendMigrationGamePeer >= peers.Num() ) {
nextSendMigrationGamePeer = 0;
}
nextSendMigrationGameTime = now + MIGRATION_GAME_DATA_INTERVAL_MS;
}
+833
View File
@@ -0,0 +1,833 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "sys_lobby.h"
idCVar net_snapshot_send_warntime( "net_snapshot_send_warntime", "500", CVAR_INTEGER, "Print warning messages if we take longer than this to send a client a snapshot." );
idCVar net_queueSnapAcks( "net_queueSnapAcks", "1", CVAR_BOOL, "" );
idCVar net_peer_throttle_mode( "net_peer_throttle_mode", "0", CVAR_INTEGER, "= 0 off, 1 = enable fixed, 2 = absolute, 3 = both" );
idCVar net_peer_throttle_minSnapSeq( "net_peer_throttle_minSnapSeq", "150", CVAR_INTEGER, "Minumum number of snapshot exchanges before throttling can be triggered" );
idCVar net_peer_throttle_bps_peer_threshold_pct( "net_peer_throttle_bps_peer_threshold_pct", "0.60", CVAR_FLOAT, "Min reported incoming bps % of sent from host that a peer must maintain before throttling kicks in" );
idCVar net_peer_throttle_bps_host_threshold( "net_peer_throttle_bps_host_threshold", "1024", CVAR_FLOAT, "Min outgoing bps of host for bps based throttling to be considered" );
idCVar net_peer_throttle_bps_decay( "net_peer_throttle_bps_decay", "0.25f", CVAR_FLOAT, "If peer exceeds this number of queued snap deltas, then throttle his effective snap rate" );
idCVar net_peer_throttle_bps_duration( "net_peer_throttle_bps_duration", "3000", CVAR_INTEGER, "If peer exceeds this number of queued snap deltas, then throttle his effective snap rate" );
idCVar net_peer_throttle_maxSnapRate( "net_peer_throttle_maxSnapRate", "4", CVAR_INTEGER, "Highest factor of server base snapRate that a client can be throttled" );
idCVar net_snap_bw_test_throttle_max_scale( "net_snap_bw_test_throttle_max_scale", "0.80", CVAR_FLOAT, "When clamping bandwidth to reported values, scale reported value by this" );
idCVar net_snap_redundant_resend_in_ms( "net_snap_redundant_resend_in_ms", "800", CVAR_INTEGER, "Delay between redundantly sending snaps during initial snap exchange" );
idCVar net_min_ping_in_ms( "net_min_ping_in_ms", "1500", CVAR_INTEGER, "Ping has to be higher than this before we consider throttling to recover" );
idCVar net_pingIncPercentBeforeRecover( "net_pingIncPercentBeforeRecover", "1.3", CVAR_FLOAT, "Percentage change increase of ping before we try to recover" );
idCVar net_maxFailedPingRecoveries( "net_maxFailedPingRecoveries", "10", CVAR_INTEGER, "Max failed ping recoveries before we stop trying" );
idCVar net_pingRecoveryThrottleTimeInSeconds( "net_pingRecoveryThrottleTimeInSeconds", "3", CVAR_INTEGER, "Throttle snaps for this amount of time in seconds to recover from ping spike" );
idCVar net_peer_timeout_loading( "net_peer_timeout_loading", "90000", CVAR_INTEGER, "time in MS to disconnect clients during loading - production only" );
/*
========================
idLobby::UpdateSnaps
========================
*/
void idLobby::UpdateSnaps() {
assert( lobbyType == GetActingGameStateLobbyType() );
SCOPED_PROFILE_EVENT( "UpdateSnaps" );
#if 0
uint64 startTimeMicroSec = Sys_Microseconds();
#endif
haveSubmittedSnaps = false;
if ( !SendCompletedSnaps() ) {
// If we weren't able to send all the submitted snaps, we need to wait till we can.
// We can't start new jobs until they are all sent out.
return;
}
for ( int p = 0; p < peers.Num(); p++ ) {
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
continue;
}
if ( peer.needToSubmitPendingSnap ) {
// Submit the snap
if ( SubmitPendingSnap( p ) ) {
peer.needToSubmitPendingSnap = false; // only clear this if we actually submitted the snap
}
}
}
#if 0
uint64 endTimeMicroSec = Sys_Microseconds();
if ( endTimeMicroSec - startTimeMicroSec > 200 ) { // .2 ms
idLib::Printf( "NET: UpdateSnaps time in ms: %f\n", (float)( endTimeMicroSec - startTimeMicroSec ) / 1000.0f);
}
#endif
}
/*
========================
idLobby::SendCompletedSnaps
This function will send send off any previously submitted pending snaps if they are ready
========================
*/
bool idLobby::SendCompletedSnaps() {
assert( lobbyType == GetActingGameStateLobbyType() );
bool sentAllSubmitted = true;
for ( int p = 0; p < peers.Num(); p++ ) {
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
continue;
}
if ( peer.snapProc->PendingSnapReadyToSend() ) {
// Check to see if there are any snaps that were submitted that need to be sent out
SendCompletedPendingSnap( p );
} else if ( IsHost() ) {
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 7, va(" ^8Peer %d pendingSnap not ready to send\n", p) );
}
if ( !peer.IsConnected() ) { // peer may have been dropped in "SendCompletedPendingSnap". ugh.
continue;
}
if ( peer.snapProc->PendingSnapReadyToSend() ) {
// If we still have a submitted snap, we know we're not done
sentAllSubmitted = false;
if ( IsHost() ) {
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^2Peer %d did not send all submitted snapshots.\n", p) );
}
}
}
return sentAllSubmitted;
}
/*
========================
idLobby::SendResources
========================
*/
bool idLobby::SendResources( int p ) {
assert( lobbyType == GetActingGameStateLobbyType() );
return false;
}
/*
========================
idLobby::SubmitPendingSnap
========================
*/
bool idLobby::SubmitPendingSnap( int p ) {
assert( lobbyType == GetActingGameStateLobbyType() );
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
return false;
}
// If the peer doesn't have the latest resource list, send it to him before sending any new snapshots
if ( SendResources( p ) ) {
return false;
}
if ( !peer.loaded ) {
return false;
}
if ( !peer.snapProc->HasPendingSnap() ) {
return false;
}
int time = Sys_Milliseconds();
int timeFromLastSub = time - peer.lastSnapJobTime;
int forceResendTime = session->GetTitleStorageInt( "net_snap_redundant_resend_in_ms", net_snap_redundant_resend_in_ms.GetInteger() );
if ( timeFromLastSub < forceResendTime && peer.snapProc->IsBusyConfirmingPartialSnap() ) {
return false;
}
peer.lastSnapJobTime = time;
assert( !peer.snapProc->PendingSnapReadyToSend() );
// Submit snapshot delta to jobs
peer.snapProc->SubmitPendingSnap( p + 1, objMemory, SNAP_OBJ_JOB_MEMORY, lzwData );
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" Submitted snapshot to jobList for peer %d. Since last jobsub: %d\n", p, timeFromLastSub ) );
return true;
}
/*
========================
idLobby::SendCompletedPendingSnap
========================
*/
void idLobby::SendCompletedPendingSnap( int p ) {
assert( lobbyType == GetActingGameStateLobbyType() );
int time = Sys_Milliseconds();
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
return;
}
if ( peer.snapProc == NULL || !peer.snapProc->PendingSnapReadyToSend() ) {
return;
}
// If we have a pending snap ready to send, we better have a pending snap
assert( peer.snapProc->HasPendingSnap() );
// Get the snap data blob now, even if we don't send it.
// This is somewhat wasteful, but we have to do this to keep the snap job pipe ready to keep doing work
// If we don't do this, this peer will cause other peers to be starved of snapshots, when they may very well be ready to send a snap
byte buffer[ MAX_SNAP_SIZE ];
int maxLength = sizeof( buffer ) - peer.packetProc->GetReliableDataSize() - 128;
int size = peer.snapProc->GetPendingSnapDelta( buffer, maxLength );
if ( !CanSendMoreData( p ) ) {
return;
}
// Can't send anymore snapshots until all fragments are sent
if ( peer.packetProc->HasMoreFragments() ) {
return;
}
// If the peer doesn't have the latest resource list, send it to him before sending any new snapshots
if ( SendResources( p ) ) {
return;
}
int timeFromJobSub = time - peer.lastSnapJobTime;
int timeFromLastSend = time - peer.lastSnapTime;
if ( timeFromLastSend > 0 ) {
peer.snapHz = 1000.0f / (float)timeFromLastSend;
} else {
peer.snapHz = 0.0f;
}
if ( net_snapshot_send_warntime.GetInteger() > 0 && peer.lastSnapTime != 0 && net_snapshot_send_warntime.GetInteger() < timeFromLastSend ) {
idLib::Printf( "NET: Took %d ms to send peer %d snapshot\n", timeFromLastSend, p );
}
if ( peer.throttleSnapsForXSeconds != 0 ) {
if ( time < peer.throttleSnapsForXSeconds ) {
return;
}
// If we were trying to recover ping, see if we succeeded
if ( peer.recoverPing != 0 ) {
if ( peer.lastPingRtt >= peer.recoverPing ) {
peer.failedPingRecoveries++;
} else {
const int peer_throttle_minSnapSeq = session->GetTitleStorageInt( "net_peer_throttle_minSnapSeq", net_peer_throttle_minSnapSeq.GetInteger() );
if ( peer.snapProc->GetFullSnapBaseSequence() > idSnapshotProcessor::INITIAL_SNAP_SEQUENCE + peer_throttle_minSnapSeq ) {
// If throttling recovered the ping
int maxRate = common->GetSnapRate() * session->GetTitleStorageInt( "net_peer_throttle_maxSnapRate", net_peer_throttle_maxSnapRate.GetInteger() );
peer.throttledSnapRate = idMath::ClampInt( common->GetSnapRate(), maxRate, peer.throttledSnapRate + common->GetSnapRate() );
}
}
}
peer.throttleSnapsForXSeconds = 0;
}
peer.lastSnapTime = time;
if ( size != 0 ) {
if ( size > 0 ) {
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 3, va("NET: (peer %d) Sending snapshot %d delta'd against %d. Since JobSub: %d Since LastSend: %d. Size: %d\n", p, peer.snapProc->GetSnapSequence(), peer.snapProc->GetBaseSequence(), timeFromJobSub, timeFromLastSend, size ) );
ProcessOutgoingMsg( p, buffer, size, false, 0 );
} else if ( size < 0 ) { // Size < 0 indicates the delta buffer filled up
// There used to be code here that would disconnect peers if they were in game and filled up the buffer
// This was causing issues in the playtests we were running (Doom 4 MP) and after some conversation
// determined that it was not needed since a timeout mechanism has been added since
ProcessOutgoingMsg( p, buffer, -size, false, 0 );
if ( peer.snapProc != NULL ) {
NET_VERBOSESNAPSHOT_PRINT( "NET: (peerNum: %d - name: %s) Resending last snapshot delta %d because his delta list filled up. Since JobSub: %d Since LastSend: %d Delta Size: %d\n", p, GetPeerName( p ), peer.snapProc->GetSnapSequence(), timeFromJobSub, timeFromLastSend, size );
}
}
}
// We calculate what our outgoing rate was for each sequence, so we can have a relative comparison
// for when the client reports what his downstream was in the same timeframe
if ( IsHost() && peer.snapProc != NULL && peer.snapProc->GetSnapSequence() > 0 ) {
//NET_VERBOSE_PRINT("^8 %i Rate: %.2f SnapSeq: %d GetBaseSequence: %d\n", lastAppendedSequence, peer.packetProc->GetOutgoingRateBytes(), peer.snapProc->GetSnapSequence(), peer.snapProc->GetBaseSequence() );
peer.sentBpsHistory[ peer.snapProc->GetSnapSequence() % MAX_BPS_HISTORY ] = peer.packetProc->GetOutgoingRateBytes();
}
}
/*
========================
idLobby::CheckPeerThrottle
========================
*/
void idLobby::CheckPeerThrottle( int p ) {
assert( lobbyType == GetActingGameStateLobbyType() );
if ( !verify( p >= 0 && p < peers.Num() ) ) {
return;
}
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
return;
}
if ( !IsHost() ) {
return;
}
if ( session->GetTitleStorageInt( "net_peer_throttle_mode", net_peer_throttle_mode.GetInteger() ) == 0 ) {
return;
}
if ( peer.receivedBps < 0.0f ) {
return;
}
int time = Sys_Milliseconds();
if ( !AllPeersHaveBaseState() ) {
return;
}
if ( verify( peer.snapProc != NULL ) ) {
const int peer_throttle_minSnapSeq = session->GetTitleStorageInt( "net_peer_throttle_minSnapSeq", net_peer_throttle_minSnapSeq.GetInteger() );
if ( peer.snapProc->GetFullSnapBaseSequence() <= idSnapshotProcessor::INITIAL_SNAP_SEQUENCE + peer_throttle_minSnapSeq ) {
return;
}
}
// This is bps throttling which compares the sent bytes per second to the reported received bps
float peer_throttle_bps_host_threshold = session->GetTitleStorageFloat( "net_peer_throttle_bps_host_threshold", net_peer_throttle_bps_host_threshold.GetFloat() );
if ( peer_throttle_bps_host_threshold > 0.0f ) {
int deltaT = idMath::ClampInt( 0, 100, time - peer.receivedThrottleTime );
if ( deltaT > 0 && peer.receivedThrottleTime > 0 && peer.receivedBpsIndex > 0 ) {
bool throttled = false;
float sentBps = peer.sentBpsHistory[ peer.receivedBpsIndex % MAX_BPS_HISTORY ];
// Min outgoing rate from server (don't throttle if we are sending < 1k)
if ( sentBps > peer_throttle_bps_host_threshold ) {
float pct = peer.receivedBps / idMath::ClampFloat( 0.01f, static_cast<float>( BANDWIDTH_REPORTING_MAX ), sentBps ); // note the receivedBps is implicitly clamped on client end to 10k/sec
/*
static int lastSeq = 0;
if ( peer.receivedBpsIndex != lastSeq ) {
NET_VERBOSE_PRINT( "%ssentBpsHistory[%d] = %.2f received: %.2f PCT: %.2f \n", ( pct > 1.0f ? "^1" : "" ), peer.receivedBpsIndex, sentBps, peer.receivedBps, pct );
}
lastSeq = peer.receivedBpsIndex;
*/
// Increase throttle time if peer is < % of what we are sending him
if ( pct < session->GetTitleStorageFloat( "net_peer_throttle_bps_peer_threshold_pct", net_peer_throttle_bps_peer_threshold_pct.GetFloat() ) ) {
peer.receivedThrottle += (float)deltaT;
throttled = true;
NET_VERBOSE_PRINT("NET: throttled... %.2f ....pct %.2f receivedBps %.2f outgoingBps %.2f, peer %i, seq %i\n", peer.receivedThrottle, pct, peer.receivedBps, sentBps, p, peer.snapProc->GetFullSnapBaseSequence() );
}
}
if ( !throttled ) {
float decayRate = session->GetTitleStorageFloat( "net_peer_throttle_bps_decay", net_peer_throttle_bps_decay.GetFloat() );
peer.receivedThrottle = Max<float>( 0.0f, peer.receivedThrottle - ( ( (float)deltaT ) * decayRate ) );
//NET_VERBOSE_PRINT("NET: !throttled... %.2f ....receivedBps %.2f outgoingBps %.2f\n", peer.receivedThrottle, peer.receivedBps, sentBps );
}
float duration = session->GetTitleStorageFloat( "net_peer_throttle_bps_duration", net_peer_throttle_bps_duration.GetFloat() );
if ( peer.receivedThrottle > duration ) {
peer.maxSnapBps = peer.receivedBps * session->GetTitleStorageFloat( "net_snap_bw_test_throttle_max_scale", net_snap_bw_test_throttle_max_scale.GetFloat() );
int maxRate = common->GetSnapRate() * session->GetTitleStorageInt( "net_peer_throttle_maxSnapRate", net_peer_throttle_maxSnapRate.GetInteger() );
if ( peer.throttledSnapRate == 0 ) {
peer.throttledSnapRate = common->GetSnapRate() * 2;
} else if ( peer.throttledSnapRate < maxRate ) {
peer.throttledSnapRate = idMath::ClampInt( common->GetSnapRate(), maxRate, peer.throttledSnapRate + common->GetSnapRate() );
}
peer.receivedThrottle = 0.0f; // Start over, so we don't immediately throttle again
}
}
peer.receivedThrottleTime = time;
}
}
/*
========================
idLobby::ApplySnapshotDelta
========================
*/
void idLobby::ApplySnapshotDelta( int p, int snapshotNumber ) {
assert( lobbyType == GetActingGameStateLobbyType() );
if ( !verify( p >= 0 && p < peers.Num() ) ) {
return;
}
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
return;
}
if ( net_queueSnapAcks.GetBool() && AllPeersHaveBaseState() ) {
// If we've reached our queue limit, force the oldest one out now
if ( snapDeltaAckQueue.Num() == snapDeltaAckQueue.Max() ) {
ApplySnapshotDeltaInternal( snapDeltaAckQueue[0].p, snapDeltaAckQueue[0].snapshotNumber );
snapDeltaAckQueue.RemoveIndex( 0 );
}
// Queue up acks, so we can spread them out over frames to lighten the load when they all come in at once
snapDeltaAck_t snapDeltaAck;
snapDeltaAck.p = p;
snapDeltaAck.snapshotNumber = snapshotNumber;
snapDeltaAckQueue.Append( snapDeltaAck );
} else {
ApplySnapshotDeltaInternal( p, snapshotNumber );
}
}
/*
========================
idLobby::ApplySnapshotDeltaInternal
========================
*/
bool idLobby::ApplySnapshotDeltaInternal( int p, int snapshotNumber ) {
assert( lobbyType == GetActingGameStateLobbyType() );
if ( !verify( p >= 0 && p < peers.Num() ) ) {
return false;
}
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
return false;
}
// on the server, player = peer number + 1, this only works as long as we don't support clients joining and leaving during game
// on the client, always 0
bool result = peer.snapProc->ApplySnapshotDelta( IsHost() ? p + 1 : 0, snapshotNumber );
if ( result && IsHost() && peer.snapProc->HasPendingSnap() ) {
// Send more of the pending snap if we have one for this peer.
// The reason we can do this, is because we know more about this peers base state now.
// And since we maxed out the optimal snap delta size, we'll now be able
// to send more data, since we assume we'll get better and better delta compression as
// our version of this peers base state approaches parity with the peers actual state.
// We don't send immediately, since we have to coordinate sending snaps for all peers in same place considering jobs.
peer.needToSubmitPendingSnap = true;
NET_VERBOSESNAPSHOT_PRINT( "NET: Sent more unsent snapshot data to peer %d for snapshot %d\n", p, snapshotNumber );
}
return result;
}
/*
========================
idLobby::SendSnapshotToPeer
========================
*/
idCVar net_forceDropSnap( "net_forceDropSnap", "0", CVAR_BOOL, "wait on snaps" );
void idLobby::SendSnapshotToPeer( idSnapShot & ss, int p ) {
assert( lobbyType == GetActingGameStateLobbyType() );
peer_t & peer = peers[p];
if ( net_forceDropSnap.GetBool() ) {
net_forceDropSnap.SetBool( false );
return;
}
if ( peer.pauseSnapshots ) {
return;
}
int time = Sys_Milliseconds();
const int throttleMode = session->GetTitleStorageInt( "net_peer_throttle_mode", net_peer_throttle_mode.GetInteger() );
// Real peer throttling based on performance
// -We throttle before sending to jobs rather than before sending
if ( ( throttleMode == 1 || throttleMode == 3 ) && peer.throttledSnapRate > 0 ) {
if ( time - peer.lastSnapJobTime < peer.throttledSnapRate / 1000 ) { // fixme /1000
// This peer is throttled, skip his snap shot
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Throttling peer %d.Skipping snapshot. Time elapsed: %d peer snap rate: %d\n", p, ( time - peer.lastSnapJobTime ), peer.throttledSnapRate ) );
return;
}
}
if ( throttleMode != 0 ) {
DetectSaturation( p );
}
if ( peer.maxSnapBps >= 0.0f && ( throttleMode == 2 || throttleMode == 3 ) ) {
if ( peer.packetProc->GetOutgoingRateBytes() > peer.maxSnapBps ) {
return;
}
}
// TrySetPendingSnapshot will try to set the new pending snap.
// TrySetPendingSnapshot won't do anything until the last snap set was fully sent out.
if ( peer.snapProc->TrySetPendingSnapshot( ss ) ) {
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^8Set next pending snapshot peer %d\n", 0 ) );
peer.numSnapsSent++;
idSnapShot * baseState = peers[p].snapProc->GetBaseState();
if ( verify( baseState != NULL ) ) {
baseState->UpdateExpectedSeq( peers[p].snapProc->GetSnapSequence() );
}
} else {
NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va(" ^2FAILED Set next pending snapshot peer %d\n", 0 ) );
}
// We send out the pending snap, which could be the most recent, or an old one that hasn't fully been sent
// We don't send immediately, since we have to coordinate sending snaps for all peers in same place considering jobs.
peer.needToSubmitPendingSnap = true;
}
/*
========================
idLobby::AllPeersHaveBaseState
========================
*/
bool idLobby::AllPeersHaveBaseState() {
assert( lobbyType == GetActingGameStateLobbyType() );
for ( int i = 0; i < peers.Num(); ++i ) {
if ( !peers[i].IsConnected() ) {
continue;
}
if ( peers[i].snapProc->GetFullSnapBaseSequence() < idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) {
return false; // If a client hasn't ack'd his first full snap, then we are still sending base state to someone
}
}
return true;
}
/*
========================
idLobby::ThrottleSnapsForXSeconds
========================
*/
void idLobby::ThrottleSnapsForXSeconds( int p, int seconds, bool recoverPing ) {
assert( lobbyType == GetActingGameStateLobbyType() );
if ( peers[p].throttleSnapsForXSeconds != 0 ) {
return; // Already throttling snaps
}
idLib::Printf( "Throttling peer %i for %i seconds...\n", p, seconds );
peers[p].throttleSnapsForXSeconds = Sys_Milliseconds() + seconds * 1000;
peers[p].recoverPing = recoverPing ? peers[p].lastPingRtt : 0;
}
/*
========================
idLobby::FirstSnapHasBeenSent
========================
*/
bool idLobby::FirstSnapHasBeenSent( int p ) {
assert( lobbyType == GetActingGameStateLobbyType() );
if ( !verify( p >= 0 && p < peers.Num() ) ) {
return false;
}
peer_t & peer = peers[p];
if ( peer.numSnapsSent == 0 ) {
return false;
}
if ( peer.snapProc == NULL ) {
return false;
}
idSnapShot * ss = peer.snapProc->GetPendingSnap();
if ( ss == NULL ) {
return false;
}
if ( ss->NumObjects() == 0 ) {
return false;
}
return true;
}
/*
========================
idLobby::EnsureAllPeersHaveBaseState
This function ensures all peers that started the match together (they were in the lobby when it started) start together.
Join in progress peers will be handled as they join.
========================
*/
bool idLobby::EnsureAllPeersHaveBaseState() {
assert( lobbyType == GetActingGameStateLobbyType() );
int time = Sys_Milliseconds();
for ( int i = 0; i < peers.Num(); ++i ) {
if ( !peers[i].IsConnected() ) {
continue;
}
if ( !FirstSnapHasBeenSent( i ) ) {
continue; // Must be join in progress peer
}
if ( peers[i].snapProc->GetFullSnapBaseSequence() < idSnapshotProcessor::INITIAL_SNAP_SEQUENCE ) {
if ( time - peers[i].lastSnapTime > session->GetTitleStorageInt( "net_snap_redundant_resend_in_ms", net_snap_redundant_resend_in_ms.GetInteger() ) ) {
SendSnapshotToPeer( *peers[i].snapProc->GetPendingSnap(), i );
}
return false;
}
}
return true;
}
/*
========================
idLobby::AllPeersHaveStaleSnapObj
========================
*/
bool idLobby::AllPeersHaveStaleSnapObj( int objId ) {
assert( lobbyType == GetActingGameStateLobbyType() );
for ( int i = 0; i < peers.Num(); i++ ) {
if ( !peers[i].IsConnected() ) {
continue;
}
idSnapShot * baseState = peers[i].snapProc->GetBaseState();
idSnapShot::objectState_t * state = baseState->FindObjectByID( objId );
if ( state == NULL || !state->stale ) {
return false;
}
}
return true;
}
/*
========================
idLobby::AllPeersHaveExpectedSnapObj
========================
*/
bool idLobby::AllPeersHaveExpectedSnapObj( int objId ) {
assert( lobbyType == GetActingGameStateLobbyType() );
for ( int i = 0; i < peers.Num(); i++ ) {
if ( !peers[i].IsConnected() ) {
continue;
}
idSnapShot * baseState = peers[i].snapProc->GetBaseState();
idSnapShot::objectState_t * state = baseState->FindObjectByID( objId );
if ( state == NULL ) {
return false;
}
if ( state->expectedSequence == -2 ) {
return false;
}
if ( state->expectedSequence > 0 && peers[i].snapProc->GetFullSnapBaseSequence() <= state->expectedSequence ) {
//idLib::Printf("^3Not ready to go stale. obj %d Base: %d expected: %d\n", objId, peers[i].snapProc->GetBaseSequence(), state->expectedSequence );
return false;
}
}
return true;
}
/*
========================
idLobby::MarkSnapObjDeleted
========================
*/
void idLobby::RefreshSnapObj( int objId ) {
assert( lobbyType == GetActingGameStateLobbyType() );
for ( int i = 0; i < peers.Num(); i++ ) {
if ( !peers[i].IsConnected() ) {
continue;
}
idSnapShot * baseState = peers[i].snapProc->GetBaseState();
idSnapShot::objectState_t * state = baseState->FindObjectByID( objId );
if ( state != NULL ) {
// Setting to -2 will defer setting the expected sequence until the current snap is ready to send
state->expectedSequence = -2;
}
}
}
/*
========================
idLobby::MarkSnapObjDeleted
========================
*/
void idLobby::MarkSnapObjDeleted( int objId ) {
assert( lobbyType == GetActingGameStateLobbyType() );
for ( int i = 0; i < peers.Num(); i++ ) {
if ( !peers[i].IsConnected() ) {
continue;
}
idSnapShot * baseState = peers[i].snapProc->GetBaseState();
idSnapShot::objectState_t * state = baseState->FindObjectByID( objId );
if ( state != NULL ) {
state->deleted = true;
}
}
}
/*
========================
idLobby::ResetBandwidthStats
========================
*/
void idLobby::ResetBandwidthStats() {
assert( lobbyType == GetActingGameStateLobbyType() );
lastSnapBspHistoryUpdateSequence = -1;
for ( int p = 0; p < peers.Num(); p++ ) {
peers[p].maxSnapBps = -1.0f;
peers[p].throttledSnapRate = 0;
peers[p].rightBeforeSnapsPing = peers[p].lastPingRtt;
peers[p].throttleSnapsForXSeconds = 0;
peers[p].recoverPing = 0;
peers[p].failedPingRecoveries = 0;
peers[p].rightBeforeSnapsPing = 0;
}
}
/*
========================
idLobby::DetectSaturation
See if the ping shot up, which indicates a previously saturated connection
========================
*/
void idLobby::DetectSaturation( int p ) {
assert( lobbyType == GetActingGameStateLobbyType() );
peer_t & peer = peers[p];
if ( !peer.IsConnected() ) {
return;
}
const float pingIncPercentBeforeThottle = session->GetTitleStorageFloat( "net_pingIncPercentBeforeRecover", net_pingIncPercentBeforeRecover.GetFloat() );
const int pingThreshold = session->GetTitleStorageInt( "net_min_ping_in_ms", net_min_ping_in_ms.GetInteger() );
const int maxFailedPingRecoveries = session->GetTitleStorageInt( "net_maxFailedPingRecoveries", net_maxFailedPingRecoveries.GetInteger() );
const int pingRecoveryThrottleTimeInSeconds = session->GetTitleStorageInt( "net_pingRecoveryThrottleTimeInSeconds", net_pingRecoveryThrottleTimeInSeconds.GetInteger() );
if ( peer.lastPingRtt > peer.rightBeforeSnapsPing * pingIncPercentBeforeThottle && peer.lastPingRtt > pingThreshold ) {
if ( peer.failedPingRecoveries < maxFailedPingRecoveries ) {
ThrottleSnapsForXSeconds( p, pingRecoveryThrottleTimeInSeconds, true );
}
}
}
/*
========================
idLobby::AddSnapObjTemplate
========================
*/
void idLobby::AddSnapObjTemplate( int objID, idBitMsg & msg ) {
assert( lobbyType == GetActingGameStateLobbyType() );
// If we are in the middle of a SS read, apply this state to what we
// just deserialized (the obj we just deserialized is a delta from the template object we are adding right now)
if ( localReadSS != NULL ) {
localReadSS->ApplyToExistingState( objID, msg );
}
// Add the template to the snapshot proc for future snapshot processing
for ( int p = 0; p < peers.Num(); p++ ) {
if ( !peers[p].IsConnected() || peers[p].snapProc == NULL ) {
continue;
}
peers[p].snapProc->AddSnapObjTemplate( objID, msg );
}
}
File diff suppressed because it is too large Load Diff
+270
View File
@@ -0,0 +1,270 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "sys_local.h"
const char * sysLanguageNames[] = {
ID_LANG_ENGLISH, ID_LANG_FRENCH, ID_LANG_ITALIAN, ID_LANG_GERMAN, ID_LANG_SPANISH, ID_LANG_JAPANESE, NULL
};
const int numLanguages = sizeof( sysLanguageNames ) / sizeof sysLanguageNames[ 0 ] - 1;
idCVar sys_lang( "sys_lang", ID_LANG_ENGLISH, CVAR_SYSTEM | CVAR_INIT, "", sysLanguageNames, idCmdSystem::ArgCompletion_String<sysLanguageNames> );
idSysLocal sysLocal;
idSys * sys = &sysLocal;
void idSysLocal::DebugPrintf( const char *fmt, ... ) {
va_list argptr;
va_start( argptr, fmt );
Sys_DebugVPrintf( fmt, argptr );
va_end( argptr );
}
void idSysLocal::DebugVPrintf( const char *fmt, va_list arg ) {
Sys_DebugVPrintf( fmt, arg );
}
double idSysLocal::GetClockTicks() {
return Sys_GetClockTicks();
}
double idSysLocal::ClockTicksPerSecond() {
return Sys_ClockTicksPerSecond();
}
cpuid_t idSysLocal::GetProcessorId() {
return Sys_GetProcessorId();
}
const char *idSysLocal::GetProcessorString() {
return Sys_GetProcessorString();
}
const char *idSysLocal::FPU_GetState() {
return Sys_FPU_GetState();
}
bool idSysLocal::FPU_StackIsEmpty() {
return Sys_FPU_StackIsEmpty();
}
void idSysLocal::FPU_SetFTZ( bool enable ) {
Sys_FPU_SetFTZ( enable );
}
void idSysLocal::FPU_SetDAZ( bool enable ) {
Sys_FPU_SetDAZ( enable );
}
bool idSysLocal::LockMemory( void *ptr, int bytes ) {
return Sys_LockMemory( ptr, bytes );
}
bool idSysLocal::UnlockMemory( void *ptr, int bytes ) {
return Sys_UnlockMemory( ptr, bytes );
}
void idSysLocal::GetCallStack( address_t *callStack, const int callStackSize ) {
Sys_GetCallStack( callStack, callStackSize );
}
const char * idSysLocal::GetCallStackStr( const address_t *callStack, const int callStackSize ) {
return Sys_GetCallStackStr( callStack, callStackSize );
}
const char * idSysLocal::GetCallStackCurStr( int depth ) {
return Sys_GetCallStackCurStr( depth );
}
void idSysLocal::ShutdownSymbols() {
Sys_ShutdownSymbols();
}
int idSysLocal::DLL_Load( const char *dllName ) {
return Sys_DLL_Load( dllName );
}
void *idSysLocal::DLL_GetProcAddress( int dllHandle, const char *procName ) {
return Sys_DLL_GetProcAddress( dllHandle, procName );
}
void idSysLocal::DLL_Unload( int dllHandle ) {
Sys_DLL_Unload( dllHandle );
}
void idSysLocal::DLL_GetFileName( const char *baseName, char *dllName, int maxLength ) {
idStr::snPrintf( dllName, maxLength, "%s" CPUSTRING ".dll", baseName );
}
sysEvent_t idSysLocal::GenerateMouseButtonEvent( int button, bool down ) {
sysEvent_t ev;
ev.evType = SE_KEY;
ev.evValue = K_MOUSE1 + button - 1;
ev.evValue2 = down;
ev.evPtrLength = 0;
ev.evPtr = NULL;
return ev;
}
sysEvent_t idSysLocal::GenerateMouseMoveEvent( int deltax, int deltay ) {
sysEvent_t ev;
ev.evType = SE_MOUSE;
ev.evValue = deltax;
ev.evValue2 = deltay;
ev.evPtrLength = 0;
ev.evPtr = NULL;
return ev;
}
void idSysLocal::FPU_EnableExceptions( int exceptions ) {
Sys_FPU_EnableExceptions( exceptions );
}
/*
=================
Sys_TimeStampToStr
=================
*/
const char *Sys_TimeStampToStr( ID_TIME_T timeStamp ) {
static char timeString[MAX_STRING_CHARS];
timeString[0] = '\0';
time_t ts = (time_t)timeStamp;
tm* time = localtime( &ts );
if ( time == NULL ) {
return "??/??/???? ??:??";
}
idStr out;
idStr lang = cvarSystem->GetCVarString( "sys_lang" );
if ( lang.Icmp( ID_LANG_ENGLISH ) == 0 ) {
// english gets "month/day/year hour:min" + "am" or "pm"
out = va( "%02d", time->tm_mon + 1 );
out += "/";
out += va( "%02d", time->tm_mday );
out += "/";
out += va( "%d", time->tm_year + 1900 );
out += " "; // changed to spaces since flash doesn't recognize \t
if ( time->tm_hour > 12 ) {
out += va( "%02d", time->tm_hour - 12 );
} else if ( time->tm_hour == 0 ) {
out += "12";
} else {
out += va( "%02d", time->tm_hour );
}
out += ":";
out +=va( "%02d", time->tm_min );
if ( time->tm_hour >= 12 ) {
out += "pm";
} else {
out += "am";
}
} else {
// europeans get "day/month/year 24hour:min"
out = va( "%02d", time->tm_mday );
out += "/";
out += va( "%02d", time->tm_mon + 1 );
out += "/";
out += va( "%d", time->tm_year + 1900 );
out += " "; // changed to spaces since flash doesn't recognize \t
out += va( "%02d", time->tm_hour );
out += ":";
out += va( "%02d", time->tm_min );
}
idStr::Copynz( timeString, out, sizeof( timeString ) );
return timeString;
}
/*
========================
Sys_SecToStr
========================
*/
const char * Sys_SecToStr( int sec ) {
static char timeString[MAX_STRING_CHARS];
int weeks = sec / ( 3600 * 24 * 7 );
sec -= weeks * ( 3600 * 24 * 7 );
int days = sec / ( 3600 * 24 );
sec -= days * ( 3600 * 24 );
int hours = sec / 3600;
sec -= hours * 3600;
int min = sec / 60;
sec -= min * 60;
if ( weeks > 0 ) {
sprintf( timeString, "%dw, %dd, %d:%02d:%02d", weeks, days, hours, min, sec );
} else if ( days > 0 ) {
sprintf( timeString, "%dd, %d:%02d:%02d", days, hours, min, sec );
} else {
sprintf( timeString, "%d:%02d:%02d", hours, min, sec );
}
return timeString;
}
// return number of supported languages
int Sys_NumLangs() {
return numLanguages;
}
// get language name by index
const char * Sys_Lang( int idx ) {
if ( idx >= 0 && idx < numLanguages ) {
return sysLanguageNames[ idx ];
}
return "";
}
const char * Sys_DefaultLanguage() {
// sku breakdowns are as follows
// EFIGS Digital
// EF S North America
// FIGS EU
// E UK
// JE Japan
// If japanese exists, default to japanese
// else if english exists, defaults to english
// otherwise, french
if ( !fileSystem->UsingResourceFiles() ) {
return ID_LANG_ENGLISH;
}
return ID_LANG_ENGLISH;
}
+76
View File
@@ -0,0 +1,76 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_LOCAL__
#define __SYS_LOCAL__
/*
==============================================================
idSysLocal
==============================================================
*/
class idSysLocal : public idSys {
public:
virtual void DebugPrintf( VERIFY_FORMAT_STRING const char *fmt, ... );
virtual void DebugVPrintf( const char *fmt, va_list arg );
virtual double GetClockTicks();
virtual double ClockTicksPerSecond();
virtual cpuid_t GetProcessorId();
virtual const char * GetProcessorString();
virtual const char * FPU_GetState();
virtual bool FPU_StackIsEmpty();
virtual void FPU_SetFTZ( bool enable );
virtual void FPU_SetDAZ( bool enable );
virtual void FPU_EnableExceptions( int exceptions );
virtual void GetCallStack( address_t *callStack, const int callStackSize );
virtual const char * GetCallStackStr( const address_t *callStack, const int callStackSize );
virtual const char * GetCallStackCurStr( int depth );
virtual void ShutdownSymbols();
virtual bool LockMemory( void *ptr, int bytes );
virtual bool UnlockMemory( void *ptr, int bytes );
virtual int DLL_Load( const char *dllName );
virtual void * DLL_GetProcAddress( int dllHandle, const char *procName );
virtual void DLL_Unload( int dllHandle );
virtual void DLL_GetFileName( const char *baseName, char *dllName, int maxLength );
virtual sysEvent_t GenerateMouseButtonEvent( int button, bool down );
virtual sysEvent_t GenerateMouseMoveEvent( int deltax, int deltay );
virtual void OpenURL( const char *url, bool quit );
virtual void StartProcess( const char *exeName, bool quit );
};
#endif /* !__SYS_LOCAL__ */
+192
View File
@@ -0,0 +1,192 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
extern idCVar fs_savepath;
/*
========================
idLocalUser::idLocalUser
========================
*/
idLocalUser::idLocalUser() {
memset( joiningLobby, 0, sizeof( joiningLobby ) );
profileMgr.Init( this );
syncAchievementsRequested = 0;
}
void idLocalUser::Pump() {
// Pump the profile
GetProfileMgr().Pump();
if ( GetProfileMgr().GetProfile() != NULL && GetProfileMgr().GetProfile()->GetState() == idPlayerProfile::IDLE ) {
// Pump achievements
if ( syncAchievementsRequested ) {
if ( session->GetAchievementSystem().IsInitialized() ) {
session->GetAchievementSystem().SyncAchievementBits( this );
syncAchievementsRequested = false;
}
}
session->GetAchievementSystem().Pump();
}
// Extra platform pump if necessary
PumpPlatform();
}
/*
========================
idLocalUser::IsStorageDeviceAvailable
========================
*/
bool idLocalUser::IsStorageDeviceAvailable() const {
return saveGame_enable.GetBool();
}
/*
========================
idLocalUser::ResetStorageDevice
========================
*/
void idLocalUser::ResetStorageDevice() {
}
/*
========================
idLocalUser::StorageSizeAvailable
========================
*/
bool idLocalUser::StorageSizeAvailable( uint64 minSizeInBytes, int64 & neededBytes ) {
int64 size = Sys_GetDriveFreeSpaceInBytes( fs_savepath.GetString() );
neededBytes = minSizeInBytes - size;
if ( neededBytes < 0 ) {
neededBytes = 0;
}
return neededBytes == 0;
}
/*
========================
idLocalUser::SetStatInt
========================
*/
void idLocalUser::SetStatInt( int s, int v ) {
idPlayerProfile * profile = GetProfile();
if ( profile != NULL ) {
return profile->StatSetInt( s, v );
}
}
/*
========================
idLocalUser::SetStatFloat
========================
*/
void idLocalUser::SetStatFloat( int s, float v ) {
idPlayerProfile * profile = GetProfile();
if ( profile != NULL ) {
return profile->StatSetFloat( s, v );
}
}
/*
========================
idLocalUser::GetStatInt
========================
*/
int idLocalUser::GetStatInt( int s ) {
const idPlayerProfile * profile = GetProfile();
if ( profile != NULL && s >= 0 ) {
return profile->StatGetInt( s );
}
return 0;
}
/*
========================
idLocalUser::GetStatFloat
========================
*/
float idLocalUser::GetStatFloat( int s ) {
const idPlayerProfile * profile = GetProfile();
if ( profile != NULL ) {
return profile->StatGetFloat( s );
}
return 0.0f;
}
/*
========================
idLocalUser::LoadProfileSettings
========================
*/
void idLocalUser::LoadProfileSettings() {
idPlayerProfile * profile = GetProfileMgr().GetProfile();
// Lazy instantiation
if ( profile == NULL ) {
// Create a new profile
profile = idPlayerProfile::CreatePlayerProfile( GetInputDevice() );
}
if ( profile != NULL ) {
profile->LoadSettings();
}
return;
}
/*
========================
idLocalUser::SaveProfileSettings
========================
*/
void idLocalUser::SaveProfileSettings() {
idPlayerProfile * profile = GetProfileMgr().GetProfile();
if ( profile != NULL ) {
profile->SaveSettings( true );
}
return;
}
/*
========================
localUserHandle_t::Serialize
========================
*/
void localUserHandle_t::Serialize( idSerializer & ser ) {
ser.Serialize( handle );
}
+149
View File
@@ -0,0 +1,149 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_LOCALUSER_H__
#define __SYS_LOCALUSER_H__
#include "sys_profile.h"
struct achievementDescription_t;
class idPlayerProfile;
class idProfileMgr;
enum onlineCaps_t {
CAP_IS_ONLINE = BIT( 0 ),
CAP_BLOCKED_PERMISSION = BIT( 1 ),
CAP_CAN_PLAY_ONLINE = BIT( 2 ),
};
class idSerializer;
/*
================================================
localUserHandle_t
================================================
*/
struct localUserHandle_t {
public:
typedef uint32 userHandleType_t;
localUserHandle_t() : handle( 0 ) {}
explicit localUserHandle_t( userHandleType_t handle_ ) : handle( handle_ ) {}
bool operator == ( const localUserHandle_t & other ) const {
return handle == other.handle;
}
bool operator < ( const localUserHandle_t & other ) const {
return handle < other.handle;
}
bool IsValid() const { return handle > 0; }
void WriteToMsg( idBitMsg & msg ) {
msg.WriteLong( handle );
}
void ReadFromMsg( const idBitMsg & msg ) {
handle = msg.ReadLong();
}
void Serialize( idSerializer & ser );
private:
userHandleType_t handle;
};
/*
================================================
idLocalUser
An idLocalUser is a user holding a controller.
It represents someone controlling the menu or game.
They may not necessarily be in a game (which would be a session user of TYPE_GAME).
A controller user references an input device (which is a gamepad, keyboard, etc).
================================================
*/
class idLocalUser {
public:
idLocalUser();
virtual ~idLocalUser() {}
void Pump();
virtual void PumpPlatform() = 0;
virtual bool IsPersistent() const { return IsProfileReady(); } // True if this user is a persistent user, and can save stats, etc (signed in)
virtual bool IsProfileReady() const = 0; // True if IsPersistent is true AND profile is signed into LIVE service
virtual bool IsOnline() const = 0; // True if this user has online capabilities
virtual uint32 GetOnlineCaps() const = 0; // Returns combination of onlineCaps_t flags
virtual bool HasOwnerChanged() const { return false; } // Whether or not the original persistent owner has changed since it was first registered
virtual int GetInputDevice() const = 0; // Input device of controller
virtual const char * GetGamerTag() const = 0; // Gamertag of user
virtual bool IsInParty() const = 0; // True if the user is in a party (do we support this on pc and ps3? )
virtual int GetPartyCount() const = 0; // Gets the amount of users in the party
// Storage related
virtual bool IsStorageDeviceAvailable() const; // Only false if the player has chosen to play without a storage device, only possible on 360, if available, everything needs to check for available space
virtual void ResetStorageDevice();
virtual bool StorageSizeAvailable( uint64 minSizeInBytes, int64 & neededBytes );
// These set stats within the profile as a enum/value pair
virtual void SetStatInt( int stat, int value );
virtual void SetStatFloat( int stat, float value );
virtual int GetStatInt( int stat );
virtual float GetStatFloat( int stat);
virtual idPlayerProfile * GetProfile() { return GetProfileMgr().GetProfile(); }
const idPlayerProfile * GetProfile() const { return const_cast< idLocalUser * >( this )->GetProfile(); }
idProfileMgr & GetProfileMgr() { return profileMgr; }
// Helper state to determine if the user is joining a party lobby or not
void SetJoiningLobby( int lobbyType, bool value ) { joiningLobby[lobbyType] = value; }
bool IsJoiningLobby( int lobbyType ) const { return joiningLobby[lobbyType]; }
bool CanPlayOnline() const { return ( GetOnlineCaps() & CAP_CAN_PLAY_ONLINE ) > 0; }
localUserHandle_t GetLocalUserHandle() const { return localUserHandle; }
void SetLocalUserHandle( localUserHandle_t newHandle ) { localUserHandle = newHandle; }
// Creates a new profile if one not already there
void LoadProfileSettings();
void SaveProfileSettings();
// Will attempt to sync the achievement bits between the server and the localUser when the achievement system is ready
void RequestSyncAchievements() { syncAchievementsRequested = true; }
private:
bool joiningLobby[2];
localUserHandle_t localUserHandle;
idProfileMgr profileMgr;
bool syncAchievementsRequested;
};
#endif // __SYS_LOCALUSER_H__
+430
View File
@@ -0,0 +1,430 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#define SAVEGAME_PROFILE_FILENAME "profile.bin"
idCVar profile_verbose( "profile_verbose", "0", CVAR_BOOL, "Turns on debug spam for profiles" );
/*
================================================
idProfileMgr
================================================
*/
/*
========================
idProfileMgr
========================
*/
idProfileMgr::idProfileMgr() :
profileSaveProcessor( new (TAG_SAVEGAMES) idSaveGameProcessorSaveProfile ),
profileLoadProcessor( new (TAG_SAVEGAMES) idSaveGameProcessorLoadProfile ),
profile( NULL ),
handle( 0 ) {
}
/*
================================================
~idProfileMgr
================================================
*/
idProfileMgr::~idProfileMgr() {
}
/*
========================
idProfileMgr::Init
========================
*/
void idProfileMgr::Init( idLocalUser * user_ ) {
user = user_;
handle = 0;
}
/*
========================
idProfileMgr::Pump
========================
*/
void idProfileMgr::Pump() {
// profile can be NULL if we forced the user to register as in the case of map-ing into a level from the press start screen
if ( profile == NULL ) {
return;
}
// See if we are done with saving/loading the profile
bool saving = profile->GetState() == idPlayerProfile::SAVING;
bool loading = profile->GetState() == idPlayerProfile::LOADING;
if ( ( saving || loading ) && session->IsSaveGameCompletedFromHandle( handle ) ) {
profile->SetState( idPlayerProfile::IDLE );
if ( saving ) {
// Done saving
} else if ( loading ) {
// Done loading
const idSaveLoadParms & parms = profileLoadProcessor->GetParms();
if ( parms.GetError() == SAVEGAME_E_FOLDER_NOT_FOUND || parms.GetError() == SAVEGAME_E_FILE_NOT_FOUND ) {
profile->SaveSettings( true );
} else if ( parms.GetError() == SAVEGAME_E_CORRUPTED ) {
idLib::Warning( "Profile corrupt, creating a new one..." );
common->Dialog().AddDialog( GDM_CORRUPT_PROFILE, DIALOG_CONTINUE, NULL, NULL, false );
profile->SetDefaults();
profile->SaveSettings( true );
} else if ( parms.GetError() != SAVEGAME_E_NONE ) {
profile->SetState( idPlayerProfile::ERR );
}
session->OnLocalUserProfileLoaded( user );
}
} else if ( saving || loading ) {
return;
}
// See if we need to save/load the profile
if ( profile->GetRequestedState() == idPlayerProfile::SAVE_REQUESTED && profile->IsDirty() ) {
profile->MarkDirty( false );
SaveSettingsAsync();
// Syncs the steam data
//session->StoreStats();
profile->SetRequestedState( idPlayerProfile::IDLE );
} else if ( profile->GetRequestedState() == idPlayerProfile::LOAD_REQUESTED ) {
LoadSettingsAsync();
profile->SetRequestedState( idPlayerProfile::IDLE );
}
}
/*
========================
idProfileMgr::GetProfile
========================
*/
idPlayerProfile * idProfileMgr::GetProfile() {
assert( user != NULL );
if ( profile == NULL ) {
// Lazy instantiation
// Create a new profile
profile = idPlayerProfile::CreatePlayerProfile( user->GetInputDevice() );
if ( profile == NULL ) {
return NULL;
}
}
bool loading = ( profile->GetState() == idPlayerProfile::LOADING ) || ( profile->GetRequestedState() == idPlayerProfile::LOAD_REQUESTED );
if ( loading ) {
return NULL;
}
return profile;
}
/*
========================
idProfileMgr::SaveSettingsAsync
========================
*/
void idProfileMgr::SaveSettingsAsync() {
if ( !saveGame_enable.GetBool() ) {
idLib::Warning( "Skipping profile save because saveGame_enable = 0" );
}
if ( GetProfile() != NULL ) {
// Issue the async save...
if ( profileSaveProcessor->InitSaveProfile( profile, "" ) ) {
profileSaveProcessor->AddCompletedCallback( MakeCallback( this, &idProfileMgr::OnSaveSettingsCompleted, &profileSaveProcessor->GetParmsNonConst() ) );
handle = session->GetSaveGameManager().ExecuteProcessor( profileSaveProcessor.get() );
profile->SetState( idPlayerProfile::SAVING );
}
} else {
idLib::Warning( "Not saving profile, profile is NULL." );
}
}
/*
========================
idProfileMgr::LoadSettingsAsync
========================
*/
void idProfileMgr::LoadSettingsAsync() {
if ( profile != NULL && saveGame_enable.GetBool() ) {
if ( profileLoadProcessor->InitLoadProfile( profile, "" ) ) {
// Skip the not found error because this might be the first time to play the game!
profileLoadProcessor->SetSkipSystemErrorDialogMask( SAVEGAME_E_FOLDER_NOT_FOUND | SAVEGAME_E_FILE_NOT_FOUND );
profileLoadProcessor->AddCompletedCallback( MakeCallback( this, &idProfileMgr::OnLoadSettingsCompleted, &profileLoadProcessor->GetParmsNonConst() ) );
handle = session->GetSaveGameManager().ExecuteProcessor( profileLoadProcessor.get() );
profile->SetState( idPlayerProfile::LOADING );
}
} else {
// If not able to save the profile, just change the state and leave
if ( profile == NULL ) {
idLib::Warning( "Not loading profile, profile is NULL." );
}
if ( !saveGame_enable.GetBool() ) {
idLib::Warning( "Skipping profile load because saveGame_enable = 0" );
}
}
}
/*
========================
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;
}
}
/*
========================
idProfileMgr::OnSaveSettingsCompleted
========================
*/
void idProfileMgr::OnSaveSettingsCompleted( idSaveLoadParms * parms ) {
common->Dialog().ShowSaveIndicator( false );
if ( parms->GetError() != SAVEGAME_E_NONE ) {
common->Dialog().AddDialog( GDM_PROFILE_SAVE_ERROR, DIALOG_CONTINUE, NULL, NULL, false );
}
if ( game ) {
game->Shell_UpdateSavedGames();
}
}
/*
================================================
idSaveGameProcessorSaveProfile
================================================
*/
/*
========================
idSaveGameProcessorSaveProfile::idSaveGameProcessorSaveProfile
========================
*/
idSaveGameProcessorSaveProfile::idSaveGameProcessorSaveProfile() {
profileFile = NULL;
profile = NULL;
}
/*
========================
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;
}
/*
========================
idSaveGameProcessorSaveProfile::Process
========================
*/
bool idSaveGameProcessorSaveProfile::Process() {
// Files already setup for save, just execute as normal files
return idSaveGameProcessorSaveFiles::Process();
}
/*
================================================
idSaveGameProcessorLoadProfile
================================================
*/
/*
========================
idSaveGameProcessorLoadProfile::idSaveGameProcessorLoadProfile
========================
*/
idSaveGameProcessorLoadProfile::idSaveGameProcessorLoadProfile() {
profileFile = NULL;
profile = NULL;
}
/*
========================
idSaveGameProcessorLoadProfile::~idSaveGameProcessorLoadProfile
========================
*/
idSaveGameProcessorLoadProfile::~idSaveGameProcessorLoadProfile() {
}
/*
========================
idSaveGameProcessorLoadProfile::InitLoadFiles
========================
*/
bool idSaveGameProcessorLoadProfile::InitLoadProfile( idPlayerProfile * profile_, const char * folder_ ) {
if ( !idSaveGameProcessor::Init() ) {
return false;
}
parms.directory = AddSaveFolderPrefix( folder_, idSaveGameManager::PACKAGE_PROFILE );
parms.description.slotName = folder_;
parms.mode = SAVEGAME_MBF_LOAD;
profileFile = new (TAG_SAVEGAMES) idFile_SaveGame( SAVEGAME_PROFILE_FILENAME, SAVEGAMEFILE_BINARY | SAVEGAMEFILE_AUTO_DELETE );
parms.files.Append( profileFile );
profile = profile_;
return true;
}
/*
========================
idSaveGameProcessorLoadProfile::Process
========================
*/
bool idSaveGameProcessorLoadProfile::Process() {
return idSaveGameProcessorLoadFiles::Process();
}
/*
========================
Sys_SaveGameProfileCheck
========================
*/
bool Sys_SaveGameProfileCheck() {
bool exists = false;
const char * saveFolder = "savegame";
if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) {
idFileList * files = fileSystem->ListFiles( saveFolder, SAVEGAME_PROFILE_FILENAME );
const idStrList & fileList = files->GetList();
for ( int i = 0; i < fileList.Num(); i++ ) {
idStr filename = fileList[i];
if ( filename == SAVEGAME_PROFILE_FILENAME ) {
exists = true;
break;
}
}
fileSystem->FreeFileList( files );
}
return exists;
}
+117
View File
@@ -0,0 +1,117 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_PROFILE_H__
#define __SYS_PROFILE_H__
#include "sys_savegame.h"
#include "sys_session_savegames.h"
class idSaveGameProcessorSaveProfile;
class idSaveGameProcessorLoadProfile;
class idLocalUser;
class idPlayerProfile;
/*
================================================
idProfileMgr
================================================
*/
class idProfileMgr {
public:
idProfileMgr();
~idProfileMgr();
// Called the first time it's asked to load
void Init( idLocalUser * user );
void Pump();
idPlayerProfile * GetProfile();
private:
void LoadSettingsAsync();
void SaveSettingsAsync();
void OnLoadSettingsCompleted( idSaveLoadParms * parms );
void OnSaveSettingsCompleted( idSaveLoadParms * parms );
private:
std::auto_ptr< idSaveGameProcessorSaveProfile > profileSaveProcessor;
std::auto_ptr< idSaveGameProcessorLoadProfile > profileLoadProcessor;
idLocalUser * user; // reference passed in
idPlayerProfile * profile;
saveGameHandle_t handle;
};
/*
================================================
idSaveGameProcessorSaveProfile
================================================
*/
class idSaveGameProcessorSaveProfile : public idSaveGameProcessorSaveFiles {
public:
DEFINE_CLASS( idSaveGameProcessorSaveProfile );
idSaveGameProcessorSaveProfile();
bool InitSaveProfile( idPlayerProfile * profile, const char * folder );
virtual bool Process();
private:
idFile_SaveGame * profileFile;
idPlayerProfile * profile;
};
/*
================================================
idSaveGameProcessorLoadProfile
================================================
*/
class idSaveGameProcessorLoadProfile : public idSaveGameProcessorLoadFiles {
public:
DEFINE_CLASS( idSaveGameProcessorLoadProfile );
idSaveGameProcessorLoadProfile();
~idSaveGameProcessorLoadProfile();
bool InitLoadProfile( idPlayerProfile * profile, const char * folder );
virtual bool Process();
private:
idFile_SaveGame * profileFile;
idPlayerProfile * profile;
};
// Synchronous check, just checks if a profile exists within the savegame location
bool Sys_SaveGameProfileCheck();
#endif
+726
View File
@@ -0,0 +1,726 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_PUBLIC__
#define __SYS_PUBLIC__
#include "../idlib/CmdArgs.h"
/*
===============================================================================
Non-portable system services.
===============================================================================
*/
enum cpuid_t {
CPUID_NONE = 0x00000,
CPUID_UNSUPPORTED = 0x00001, // unsupported (386/486)
CPUID_GENERIC = 0x00002, // unrecognized processor
CPUID_INTEL = 0x00004, // Intel
CPUID_AMD = 0x00008, // AMD
CPUID_MMX = 0x00010, // Multi Media Extensions
CPUID_3DNOW = 0x00020, // 3DNow!
CPUID_SSE = 0x00040, // Streaming SIMD Extensions
CPUID_SSE2 = 0x00080, // Streaming SIMD Extensions 2
CPUID_SSE3 = 0x00100, // Streaming SIMD Extentions 3 aka Prescott's New Instructions
CPUID_ALTIVEC = 0x00200, // AltiVec
CPUID_HTT = 0x01000, // Hyper-Threading Technology
CPUID_CMOV = 0x02000, // Conditional Move (CMOV) and fast floating point comparison (FCOMI) instructions
CPUID_FTZ = 0x04000, // Flush-To-Zero mode (denormal results are flushed to zero)
CPUID_DAZ = 0x08000, // Denormals-Are-Zero mode (denormal source operands are set to zero)
CPUID_XENON = 0x10000, // Xbox 360
CPUID_CELL = 0x20000 // PS3
};
enum fpuExceptions_t {
FPU_EXCEPTION_INVALID_OPERATION = 1,
FPU_EXCEPTION_DENORMALIZED_OPERAND = 2,
FPU_EXCEPTION_DIVIDE_BY_ZERO = 4,
FPU_EXCEPTION_NUMERIC_OVERFLOW = 8,
FPU_EXCEPTION_NUMERIC_UNDERFLOW = 16,
FPU_EXCEPTION_INEXACT_RESULT = 32
};
enum fpuPrecision_t {
FPU_PRECISION_SINGLE = 0,
FPU_PRECISION_DOUBLE = 1,
FPU_PRECISION_DOUBLE_EXTENDED = 2
};
enum fpuRounding_t {
FPU_ROUNDING_TO_NEAREST = 0,
FPU_ROUNDING_DOWN = 1,
FPU_ROUNDING_UP = 2,
FPU_ROUNDING_TO_ZERO = 3
};
enum joystickAxis_t {
AXIS_LEFT_X,
AXIS_LEFT_Y,
AXIS_RIGHT_X,
AXIS_RIGHT_Y,
AXIS_LEFT_TRIG,
AXIS_RIGHT_TRIG,
MAX_JOYSTICK_AXIS
};
enum sysEventType_t {
SE_NONE, // evTime is still valid
SE_KEY, // evValue is a key code, evValue2 is the down flag
SE_CHAR, // evValue is an ascii char
SE_MOUSE, // evValue and evValue2 are reletive signed x / y moves
SE_MOUSE_ABSOLUTE, // evValue and evValue2 are absolute coordinates in the window's client area.
SE_MOUSE_LEAVE, // evValue and evValue2 are meaninless, this indicates the mouse has left the client area.
SE_JOYSTICK, // evValue is an axis number and evValue2 is the current state (-127 to 127)
SE_CONSOLE // evPtr is a char*, from typing something at a non-game console
};
enum sys_mEvents {
M_ACTION1,
M_ACTION2,
M_ACTION3,
M_ACTION4,
M_ACTION5,
M_ACTION6,
M_ACTION7,
M_ACTION8,
M_DELTAX,
M_DELTAY,
M_DELTAZ,
M_INVALID
};
enum sys_jEvents {
J_ACTION1,
J_ACTION2,
J_ACTION3,
J_ACTION4,
J_ACTION5,
J_ACTION6,
J_ACTION7,
J_ACTION8,
J_ACTION9,
J_ACTION10,
J_ACTION11,
J_ACTION12,
J_ACTION13,
J_ACTION14,
J_ACTION15,
J_ACTION16,
J_ACTION17,
J_ACTION18,
J_ACTION19,
J_ACTION20,
J_ACTION21,
J_ACTION22,
J_ACTION23,
J_ACTION24,
J_ACTION25,
J_ACTION26,
J_ACTION27,
J_ACTION28,
J_ACTION29,
J_ACTION30,
J_ACTION31,
J_ACTION32,
J_ACTION_MAX = J_ACTION32,
J_AXIS_MIN,
J_AXIS_LEFT_X = J_AXIS_MIN + AXIS_LEFT_X,
J_AXIS_LEFT_Y = J_AXIS_MIN + AXIS_LEFT_Y,
J_AXIS_RIGHT_X = J_AXIS_MIN + AXIS_RIGHT_X,
J_AXIS_RIGHT_Y = J_AXIS_MIN + AXIS_RIGHT_Y,
J_AXIS_LEFT_TRIG = J_AXIS_MIN + AXIS_LEFT_TRIG,
J_AXIS_RIGHT_TRIG = J_AXIS_MIN + AXIS_RIGHT_TRIG,
J_AXIS_MAX = J_AXIS_MIN + MAX_JOYSTICK_AXIS - 1,
J_DPAD_UP,
J_DPAD_DOWN,
J_DPAD_LEFT,
J_DPAD_RIGHT,
MAX_JOY_EVENT
};
/*
================================================
The first part of this table maps directly to Direct Input scan codes (DIK_* from dinput.h)
But they are duplicated here for console portability
================================================
*/
enum keyNum_t {
K_NONE,
K_ESCAPE,
K_1,
K_2,
K_3,
K_4,
K_5,
K_6,
K_7,
K_8,
K_9,
K_0,
K_MINUS,
K_EQUALS,
K_BACKSPACE,
K_TAB,
K_Q,
K_W,
K_E,
K_R,
K_T,
K_Y,
K_U,
K_I,
K_O,
K_P,
K_LBRACKET,
K_RBRACKET,
K_ENTER,
K_LCTRL,
K_A,
K_S,
K_D,
K_F,
K_G,
K_H,
K_J,
K_K,
K_L,
K_SEMICOLON,
K_APOSTROPHE,
K_GRAVE,
K_LSHIFT,
K_BACKSLASH,
K_Z,
K_X,
K_C,
K_V,
K_B,
K_N,
K_M,
K_COMMA,
K_PERIOD,
K_SLASH,
K_RSHIFT,
K_KP_STAR,
K_LALT,
K_SPACE,
K_CAPSLOCK,
K_F1,
K_F2,
K_F3,
K_F4,
K_F5,
K_F6,
K_F7,
K_F8,
K_F9,
K_F10,
K_NUMLOCK,
K_SCROLL,
K_KP_7,
K_KP_8,
K_KP_9,
K_KP_MINUS,
K_KP_4,
K_KP_5,
K_KP_6,
K_KP_PLUS,
K_KP_1,
K_KP_2,
K_KP_3,
K_KP_0,
K_KP_DOT,
K_F11 = 0x57,
K_F12 = 0x58,
K_F13 = 0x64,
K_F14 = 0x65,
K_F15 = 0x66,
K_KANA = 0x70,
K_CONVERT = 0x79,
K_NOCONVERT = 0x7B,
K_YEN = 0x7D,
K_KP_EQUALS = 0x8D,
K_CIRCUMFLEX = 0x90,
K_AT = 0x91,
K_COLON = 0x92,
K_UNDERLINE = 0x93,
K_KANJI = 0x94,
K_STOP = 0x95,
K_AX = 0x96,
K_UNLABELED = 0x97,
K_KP_ENTER = 0x9C,
K_RCTRL = 0x9D,
K_KP_COMMA = 0xB3,
K_KP_SLASH = 0xB5,
K_PRINTSCREEN = 0xB7,
K_RALT = 0xB8,
K_PAUSE = 0xC5,
K_HOME = 0xC7,
K_UPARROW = 0xC8,
K_PGUP = 0xC9,
K_LEFTARROW = 0xCB,
K_RIGHTARROW = 0xCD,
K_END = 0xCF,
K_DOWNARROW = 0xD0,
K_PGDN = 0xD1,
K_INS = 0xD2,
K_DEL = 0xD3,
K_LWIN = 0xDB,
K_RWIN = 0xDC,
K_APPS = 0xDD,
K_POWER = 0xDE,
K_SLEEP = 0xDF,
//------------------------
// K_JOY codes must be contiguous, too
//------------------------
K_JOY1 = 256,
K_JOY2,
K_JOY3,
K_JOY4,
K_JOY5,
K_JOY6,
K_JOY7,
K_JOY8,
K_JOY9,
K_JOY10,
K_JOY11,
K_JOY12,
K_JOY13,
K_JOY14,
K_JOY15,
K_JOY16,
K_JOY_STICK1_UP,
K_JOY_STICK1_DOWN,
K_JOY_STICK1_LEFT,
K_JOY_STICK1_RIGHT,
K_JOY_STICK2_UP,
K_JOY_STICK2_DOWN,
K_JOY_STICK2_LEFT,
K_JOY_STICK2_RIGHT,
K_JOY_TRIGGER1,
K_JOY_TRIGGER2,
K_JOY_DPAD_UP,
K_JOY_DPAD_DOWN,
K_JOY_DPAD_LEFT,
K_JOY_DPAD_RIGHT,
//------------------------
// K_MOUSE enums must be contiguous (no char codes in the middle)
//------------------------
K_MOUSE1,
K_MOUSE2,
K_MOUSE3,
K_MOUSE4,
K_MOUSE5,
K_MOUSE6,
K_MOUSE7,
K_MOUSE8,
K_MWHEELDOWN,
K_MWHEELUP,
K_LAST_KEY
};
struct sysEvent_t {
sysEventType_t evType;
int evValue;
int evValue2;
int evPtrLength; // bytes of data pointed to by evPtr, for journaling
void * evPtr; // this must be manually freed if not NULL
int inputDevice;
bool IsKeyEvent() const { return evType == SE_KEY; }
bool IsMouseEvent() const { return evType == SE_MOUSE; }
bool IsCharEvent() const { return evType == SE_CHAR; }
bool IsJoystickEvent() const { return evType == SE_JOYSTICK; }
bool IsKeyDown() const { return evValue2 != 0; }
keyNum_t GetKey() const { return static_cast< keyNum_t >( evValue ); }
int GetXCoord() const { return evValue; }
int GetYCoord() const { return evValue2; }
};
struct sysMemoryStats_t {
int memoryLoad;
int totalPhysical;
int availPhysical;
int totalPageFile;
int availPageFile;
int totalVirtual;
int availVirtual;
int availExtendedVirtual;
};
typedef unsigned long address_t;
void Sys_Init();
void Sys_Shutdown();
void Sys_Error( const char *error, ...);
const char * Sys_GetCmdLine();
void Sys_ReLaunch( void * launchData, unsigned int launchDataSize );
void Sys_Launch( const char * path, idCmdArgs & args, void * launchData, unsigned int launchDataSize );
void Sys_SetLanguageFromSystem();
const char * Sys_DefaultLanguage();
void Sys_Quit();
bool Sys_AlreadyRunning();
// note that this isn't journaled...
char * Sys_GetClipboardData();
void Sys_SetClipboardData( const char *string );
// will go to the various text consoles
// NOT thread safe - never use in the async paths
void Sys_Printf( VERIFY_FORMAT_STRING const char *msg, ... );
// guaranteed to be thread-safe
void Sys_DebugPrintf( VERIFY_FORMAT_STRING const char *fmt, ... );
void Sys_DebugVPrintf( const char *fmt, va_list arg );
// a decent minimum sleep time to avoid going below the process scheduler speeds
#define SYS_MINSLEEP 20
// allow game to yield CPU time
// NOTE: due to SYS_MINSLEEP this is very bad portability karma, and should be completely removed
void Sys_Sleep( int msec );
// Sys_Milliseconds should only be used for profiling purposes,
// any game related timing information should come from event timestamps
int Sys_Milliseconds();
uint64 Sys_Microseconds();
// for accurate performance testing
double Sys_GetClockTicks();
double Sys_ClockTicksPerSecond();
// returns a selection of the CPUID_* flags
cpuid_t Sys_GetProcessorId();
const char * Sys_GetProcessorString();
// returns true if the FPU stack is empty
bool Sys_FPU_StackIsEmpty();
// empties the FPU stack
void Sys_FPU_ClearStack();
// returns the FPU state as a string
const char * Sys_FPU_GetState();
// enables the given FPU exceptions
void Sys_FPU_EnableExceptions( int exceptions );
// sets the FPU precision
void Sys_FPU_SetPrecision( int precision );
// sets the FPU rounding mode
void Sys_FPU_SetRounding( int rounding );
// sets Flush-To-Zero mode (only available when CPUID_FTZ is set)
void Sys_FPU_SetFTZ( bool enable );
// sets Denormals-Are-Zero mode (only available when CPUID_DAZ is set)
void Sys_FPU_SetDAZ( bool enable );
// returns amount of system ram
int Sys_GetSystemRam();
// returns amount of video ram
int Sys_GetVideoRam();
// returns amount of drive space in path
int Sys_GetDriveFreeSpace( const char *path );
// returns amount of drive space in path in bytes
int64 Sys_GetDriveFreeSpaceInBytes( const char * path );
// returns memory stats
void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats );
void Sys_GetExeLaunchMemoryStatus( sysMemoryStats_t &stats );
// lock and unlock memory
bool Sys_LockMemory( void *ptr, int bytes );
bool Sys_UnlockMemory( void *ptr, int bytes );
// set amount of physical work memory
void Sys_SetPhysicalWorkMemory( int minBytes, int maxBytes );
// allows retrieving the call stack at execution points
void Sys_GetCallStack( address_t *callStack, const int callStackSize );
const char * Sys_GetCallStackStr( const address_t *callStack, const int callStackSize );
const char * Sys_GetCallStackCurStr( int depth );
const char * Sys_GetCallStackCurAddressStr( int depth );
void Sys_ShutdownSymbols();
// DLL loading, the path should be a fully qualified OS path to the DLL file to be loaded
int Sys_DLL_Load( const char *dllName );
void * Sys_DLL_GetProcAddress( int dllHandle, const char *procName );
void Sys_DLL_Unload( int dllHandle );
// event generation
void Sys_GenerateEvents();
sysEvent_t Sys_GetEvent();
void Sys_ClearEvents();
// input is tied to windows, so it needs to be started up and shut down whenever
// the main window is recreated
void Sys_InitInput();
void Sys_ShutdownInput();
// keyboard input polling
int Sys_PollKeyboardInputEvents();
int Sys_ReturnKeyboardInputEvent( const int n, int &ch, bool &state );
void Sys_EndKeyboardInputEvents();
// mouse input polling
static const int MAX_MOUSE_EVENTS = 256;
int Sys_PollMouseInputEvents( int mouseEvents[MAX_MOUSE_EVENTS][2] );
// joystick input polling
void Sys_SetRumble( int device, int low, int hi );
int Sys_PollJoystickInputEvents( int deviceNum );
int Sys_ReturnJoystickInputEvent( const int n, int &action, int &value );
void Sys_EndJoystickInputEvents();
// when the console is down, or the game is about to perform a lengthy
// operation like map loading, the system can release the mouse cursor
// when in windowed mode
void Sys_GrabMouseCursor( bool grabIt );
void Sys_ShowWindow( bool show );
bool Sys_IsWindowVisible();
void Sys_ShowConsole( int visLevel, bool quitOnClose );
// This really isn't the right place to have this, but since this is the 'top level' include
// and has a function signature with 'FILE' in it, it kinda needs to be here =/
typedef HANDLE idFileHandle;
ID_TIME_T Sys_FileTimeStamp( idFileHandle fp );
// NOTE: do we need to guarantee the same output on all platforms?
const char * Sys_TimeStampToStr( ID_TIME_T timeStamp );
const char * Sys_SecToStr( int sec );
const char * Sys_DefaultBasePath();
const char * Sys_DefaultSavePath();
// know early if we are performing a fatal error shutdown so the error message doesn't get lost
void Sys_SetFatalError( const char *error );
// Execute the specified process and wait until it's done, calling workFn every waitMS milliseconds.
// If showOutput == true, std IO from the executed process will be output to the console.
// Note that the return value is not an indication of the exit code of the process, but is false
// only if the process could not be created at all. If you wish to check the exit code of the
// spawned process, check the value returned in exitCode.
typedef bool ( *execProcessWorkFunction_t )();
typedef void ( *execOutputFunction_t)( const char * text );
bool Sys_Exec( const char * appPath, const char * workingPath, const char * args,
execProcessWorkFunction_t workFn, execOutputFunction_t outputFn, const int waitMS,
unsigned int & exitCode );
// localization
#define ID_LANG_ENGLISH "english"
#define ID_LANG_FRENCH "french"
#define ID_LANG_ITALIAN "italian"
#define ID_LANG_GERMAN "german"
#define ID_LANG_SPANISH "spanish"
#define ID_LANG_JAPANESE "japanese"
int Sys_NumLangs();
const char * Sys_Lang( int idx );
/*
==============================================================
Networking
==============================================================
*/
typedef enum {
NA_BAD, // an address lookup failed
NA_LOOPBACK,
NA_BROADCAST,
NA_IP
} netadrtype_t;
typedef struct {
netadrtype_t type;
unsigned char ip[4];
unsigned short port;
} netadr_t;
#define PORT_ANY -1
/*
================================================
idUDP
================================================
*/
class idUDP {
public:
// this just zeros netSocket and port
idUDP();
virtual ~idUDP();
// if the InitForPort fails, the idUDP.port field will remain 0
bool InitForPort( int portNumber );
int GetPort() const { return bound_to.port; }
netadr_t GetAdr() const { return bound_to; }
uint32 GetUIntAdr() const { return ( bound_to.ip[0] | bound_to.ip[1] << 8 | bound_to.ip[2] << 16 | bound_to.ip[3] << 24 ); }
void Close();
bool GetPacket( netadr_t &from, void *data, int &size, int maxSize );
bool GetPacketBlocking( netadr_t &from, void *data, int &size, int maxSize,
int timeout );
void SendPacket( const netadr_t to, const void *data, int size );
void SetSilent( bool silent ) { this->silent = silent; }
bool GetSilent() const { return silent; }
int packetsRead;
int bytesRead;
int packetsWritten;
int bytesWritten;
bool IsOpen() const { return netSocket > 0; }
private:
netadr_t bound_to; // interface and port
int netSocket; // OS specific socket
bool silent; // don't emit anything ( black hole )
};
// parses the port number
// can also do DNS resolve if you ask for it.
// NOTE: DNS resolve is a slow/blocking call, think before you use
// ( could be exploited for server DoS )
bool Sys_StringToNetAdr( const char *s, netadr_t *a, bool doDNSResolve );
const char * Sys_NetAdrToString( const netadr_t a );
bool Sys_IsLANAddress( const netadr_t a );
bool Sys_CompareNetAdrBase( const netadr_t a, const netadr_t b );
int Sys_GetLocalIPCount();
const char * Sys_GetLocalIP( int i );
void Sys_InitNetworking();
void Sys_ShutdownNetworking();
/*
================================================
idJoystick is managed by each platform's local Sys implementation, and
provides full *Joy Pad* support (the most common device, these days).
================================================
*/
class idJoystick {
public:
virtual ~idJoystick() { }
virtual bool Init() { return false; }
virtual void Shutdown() { }
virtual void Deactivate() { }
virtual void SetRumble( int deviceNum, int rumbleLow, int rumbleHigh ) { }
virtual int PollInputEvents( int inputDeviceNum ) { return 0; }
virtual int ReturnInputEvent( const int n, int &action, int &value ) { return 0; }
virtual void EndInputEvents() { }
};
/*
==============================================================
idSys
==============================================================
*/
class idSys {
public:
virtual void DebugPrintf( VERIFY_FORMAT_STRING const char *fmt, ... ) = 0;
virtual void DebugVPrintf( const char *fmt, va_list arg ) = 0;
virtual double GetClockTicks() = 0;
virtual double ClockTicksPerSecond() = 0;
virtual cpuid_t GetProcessorId() = 0;
virtual const char * GetProcessorString() = 0;
virtual const char * FPU_GetState() = 0;
virtual bool FPU_StackIsEmpty() = 0;
virtual void FPU_SetFTZ( bool enable ) = 0;
virtual void FPU_SetDAZ( bool enable ) = 0;
virtual void FPU_EnableExceptions( int exceptions ) = 0;
virtual bool LockMemory( void *ptr, int bytes ) = 0;
virtual bool UnlockMemory( void *ptr, int bytes ) = 0;
virtual void GetCallStack( address_t *callStack, const int callStackSize ) = 0;
virtual const char * GetCallStackStr( const address_t *callStack, const int callStackSize ) = 0;
virtual const char * GetCallStackCurStr( int depth ) = 0;
virtual void ShutdownSymbols() = 0;
virtual int DLL_Load( const char *dllName ) = 0;
virtual void * DLL_GetProcAddress( int dllHandle, const char *procName ) = 0;
virtual void DLL_Unload( int dllHandle ) = 0;
virtual void DLL_GetFileName( const char *baseName, char *dllName, int maxLength ) = 0;
virtual sysEvent_t GenerateMouseButtonEvent( int button, bool down ) = 0;
virtual sysEvent_t GenerateMouseMoveEvent( int deltax, int deltay ) = 0;
virtual void OpenURL( const char *url, bool quit ) = 0;
virtual void StartProcess( const char *exePath, bool quit ) = 0;
};
extern idSys * sys;
bool Sys_LoadOpenAL();
void Sys_FreeOpenAL();
#endif /* !__SYS_PUBLIC__ */
+903
View File
@@ -0,0 +1,903 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "sys_session_local.h"
idCVar saveGame_verbose( "saveGame_verbose", "0", CVAR_BOOL | CVAR_ARCHIVE, "debug spam" );
idCVar saveGame_checksum( "saveGame_checksum", "1", CVAR_BOOL, "data integrity check" );
idCVar saveGame_enable( "saveGame_enable", "1", CVAR_BOOL, "are savegames enabled" );
void Sys_ExecuteSavegameCommandAsyncImpl( idSaveLoadParms * savegameParms );
/*
========================
Sys_ExecuteSavegameCommandAsync
========================
*/
void Sys_ExecuteSavegameCommandAsync( idSaveLoadParms * savegameParms ) {
if ( savegameParms == NULL ) {
idLib::Error( "Programming Error with [%s]", __FUNCTION__ );
return;
}
if ( !saveGame_enable.GetBool() ) {
idLib::Warning( "Savegames are disabled (saveGame_enable = 0). Skipping physical save to media." );
savegameParms->errorCode = SAVEGAME_E_CANCELLED;
savegameParms->callbackSignal.Raise();
return;
}
Sys_ExecuteSavegameCommandAsyncImpl( savegameParms );
}
#define ASSERT_ENUM_STRING_BITFIELD( string, index ) ( 1 / (int)!( string - ( 1 << index ) ) ) ? #string : ""
const char * saveGameErrorStrings[ SAVEGAME_E_NUM ] = {
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_CANCELLED, 0 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INSUFFICIENT_ROOM, 1 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_CORRUPTED, 2 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE, 3 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_UNKNOWN, 4 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INVALID_FILENAME, 5 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_STEAM_ERROR, 6 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_FOLDER_NOT_FOUND, 7 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_FILE_NOT_FOUND, 8 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_DLC_NOT_FOUND, 9 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INVALID_USER, 10 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_PROFILE_TOO_BIG, 11 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_DISC_SWAP, 12 ),
ASSERT_ENUM_STRING_BITFIELD( SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION, 13 ),
};
CONSOLE_COMMAND( savegamePrintErrors, "Prints error code corresponding to each bit", 0 ) {
idLib::Printf( "Bit Description\n"
"--- -----------\n" );
for ( int i = 0; i < SAVEGAME_E_BITS_USED; i++ ) {
idLib::Printf( "%03d %s\n", i, saveGameErrorStrings[i] );
}
}
/*
========================
GetSaveGameErrorString
This returns a comma delimited string of all the errors within the bitfield. We don't ever expect more than one error,
the errors are bitfields so that they can be used to mask which errors we want to handle in the engine or leave up to the
game.
Example:
SAVEGAME_E_LOAD, SAVEGAME_E_INVALID_FILENAME
========================
*/
idStr GetSaveGameErrorString( int errorMask ) {
idStr errorString;
bool continueProcessing = errorMask > 0;
int localError = errorMask;
for ( int i = 0; i < SAVEGAME_E_NUM && continueProcessing; ++i ) {
int mask = ( 1 << i );
if ( localError & mask ) {
localError ^= mask; // turn off this error so we can quickly see if we are done
continueProcessing = localError > 0;
errorString.Append( saveGameErrorStrings[i] );
if ( continueProcessing ) {
errorString.Append( ", " );
}
}
}
return errorString;
}
/*
========================
GetSaveFolder
Prefixes help the PS3 filter what to show in lists
Files using the hidden prefix are not shown in the load game screen
Directory name for savegames, slot number or user-defined name appended to it
TRC R116 - PS3 folder must start with the product code
========================
*/
const idStr & GetSaveFolder( idSaveGameManager::packageType_t type ) {
static bool initialized = false;
static idStrStatic<MAX_FOLDER_NAME_LENGTH> saveFolder[idSaveGameManager::PACKAGE_NUM];
if ( !initialized ) {
initialized = true;
idStr ps3Header = "";
saveFolder[idSaveGameManager::PACKAGE_GAME].Format( "%s%s", ps3Header.c_str(), SAVEGAME_GAME_DIRECTORY_PREFIX );
saveFolder[idSaveGameManager::PACKAGE_PROFILE].Format( "%s%s", ps3Header.c_str(), SAVEGAME_PROFILE_DIRECTORY_PREFIX );
saveFolder[idSaveGameManager::PACKAGE_RAW].Format( "%s%s", ps3Header.c_str(), SAVEGAME_RAW_DIRECTORY_PREFIX );
}
return saveFolder[type];
}
/*
========================
idStr AddSaveFolderPrefix
input = RAGE_0
output = GAMES-RAGE_0
========================
*/
idStr AddSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type ) {
idStr dir = GetSaveFolder( type );
dir.Append( folder );
return dir;
}
/*
========================
RemoveSaveFolderPrefix
input = GAMES-RAGE_0
output = RAGE_0
========================
*/
idStr RemoveSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type ) {
idStr dir = folder;
idStr prefix = GetSaveFolder( type );
dir.StripLeading( prefix );
return dir;
}
/*
========================
bool SavegameReadDetailsFromFile
returns false when catastrophic error occurs, not when damaged
========================
*/
bool SavegameReadDetailsFromFile( idFile * file, idSaveGameDetails & details ) {
details.damaged = false;
// Read the DETAIL file for the enumerated data
if ( !details.descriptors.ReadFromIniFile( file ) ) {
details.damaged = true;
}
bool ignoreChecksum = details.descriptors.GetBool( "ignore_checksum", false );
if ( !ignoreChecksum ) {
// Get the checksum from the dict
int readChecksum = details.descriptors.GetInt( SAVEGAME_DETAIL_FIELD_CHECKSUM, 0 );
// Calculate checksum
details.descriptors.Delete( SAVEGAME_DETAIL_FIELD_CHECKSUM );
int checksum = (int)details.descriptors.Checksum();
if ( readChecksum == 0 || checksum != readChecksum ) {
details.damaged = true;
}
}
return true;
}
/*
========================
idSaveGameDetails::idSaveGameDetails
========================
*/
idSaveGameDetails::idSaveGameDetails() {
Clear();
}
/*
========================
idSaveGameDetails::Clear
========================
*/
void idSaveGameDetails::Clear() {
descriptors.Clear();
damaged = false;
date = 0;
slotName[0] = NULL;
}
/*
========================
idSaveLoadParms::idSaveLoadParms
========================
*/
idSaveLoadParms::idSaveLoadParms() {
// These are not done when we set defaults because SetDefaults is called internally within the execution of the processor and
// these are set once and shouldn't be touched until the processor is re-initialized
cancelled = false;
Init();
}
/*
========================
idSaveLoadParms::~idSaveLoadParms
========================
*/
idSaveLoadParms::~idSaveLoadParms() {
for ( int i = 0; i < files.Num(); ++i ) {
if ( files[i]->type & SAVEGAMEFILE_AUTO_DELETE ) {
delete files[i];
}
}
}
/*
========================
idSaveLoadParms::ResetCancelled
========================
*/
void idSaveLoadParms::ResetCancelled() {
cancelled = false;
}
/*
========================
idSaveLoadParms::Init
This should not touch anything statically created outside this class!
========================
*/
void idSaveLoadParms::Init() {
files.Clear();
mode = SAVEGAME_MBF_NONE;
directory = "";
pattern = "";
postPattern = "";
requiredSpaceInBytes = 0;
description.Clear();
detailList.Clear();
callbackSignal.Clear();
errorCode = SAVEGAME_E_NONE;
inputDeviceId = -1;
skipErrorDialogMask = 0;
// These are not done when we set defaults because SetDefaults is called internally within the execution of the processor and
// these are set once and shouldn't be touched until the processor is re-initialized
// cancelled = false;
}
/*
========================
idSaveLoadParms::SetDefaults
========================
*/
void idSaveLoadParms::SetDefaults( int newInputDevice ) {
// These are pulled out so SetDefaults() isn't called during global instantiation of objects that have savegame processors
// in them that then require a session reference.
Init();
// fill in the user information (inputDeviceId & userId) from the master user
idLocalUser * user = NULL;
if ( newInputDevice != -1 ) {
user = session->GetSignInManager().GetLocalUserByInputDevice( newInputDevice );
} else if ( session != NULL ) {
user = session->GetSignInManager().GetMasterLocalUser();
}
if ( user != NULL ) {
idLocalUserWin * userWin = static_cast< idLocalUserWin * >( user );
userId = idStr::Hash( userWin->GetGamerTag() );
idLib::PrintfIf( saveGame_verbose.GetBool(), "profile userId/gamertag: %s (%d)\n", userWin->GetGamerTag(), userId );
inputDeviceId = user->GetInputDevice();
}
}
/*
========================
idSaveLoadParms::CancelSaveGameFilePipelines
========================
*/
void idSaveLoadParms::CancelSaveGameFilePipelines() {
for ( int i = 0; i < files.Num(); i++ ) {
if ( ( files[i]->type & SAVEGAMEFILE_PIPELINED ) != 0 ) {
idFile_SaveGamePipelined * file = dynamic_cast< idFile_SaveGamePipelined * >( files[i] );
assert( file != NULL );
if ( file->GetMode() == idFile_SaveGamePipelined::WRITE ) {
// Notify the save game file that all writes failed which will cause all
// writes on the other end of the pipeline to drop on the floor.
file->NextWriteBlock( NULL );
} else if ( file->GetMode() == idFile_SaveGamePipelined::READ ) {
// 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.
file->NextReadBlock( NULL, 0 );
}
}
}
}
/*
========================
idSaveLoadParms::AbortSaveGameFilePipeline
========================
*/
void idSaveLoadParms::AbortSaveGameFilePipeline() {
for ( int i = 0; i < files.Num(); i++ ) {
if ( ( files[i]->type & SAVEGAMEFILE_PIPELINED ) != 0 ) {
idFile_SaveGamePipelined * file = dynamic_cast< idFile_SaveGamePipelined * >( files[i] );
assert( file != NULL );
file->Abort();
}
}
}
/*
================================================================================================
idSaveGameProcessor
================================================================================================
*/
/*
========================
idSaveGameProcessor::idSaveGameProcessor
========================
*/
idSaveGameProcessor::idSaveGameProcessor() : working( false ), init( false ) {
}
/*
========================
idSaveGameProcessor::Init
========================
*/
bool idSaveGameProcessor::Init() {
if ( !verify( !IsWorking() ) ) {
idLib::Warning( "[%s] Someone is trying to execute this processor twice, this is really bad!", this->Name() );
return false;
}
parms.ResetCancelled();
parms.SetDefaults();
savegameLogicTestIterator = 0;
working = false;
init = true;
completedCallbacks.Clear();
return true;
}
/*
========================
idSaveGameProcessor::IsThreadFinished
========================
*/
bool idSaveGameProcessor::IsThreadFinished() {
return parms.callbackSignal.Wait( 0 );
}
/*
========================
idSaveGameProcessor::AddCompletedCallback
========================
*/
void idSaveGameProcessor::AddCompletedCallback( const idCallback & callback ) {
completedCallbacks.Append( callback.Clone() );
}
/*
================================================================================================
idSaveGameManager
================================================================================================
*/
/*
========================
idSaveGameManager::idSaveGameManager
========================
*/
idSaveGameManager::idSaveGameManager() :
processor( NULL ),
cancel( false ),
startTime( 0 ),
continueProcessing( false ),
submittedProcessorHandle( 0 ),
executingProcessorHandle( 0 ),
lastExecutedProcessorHandle( 0 ),
storageAvailable( true ),
retryFolder( NULL ) {
}
/*
========================
idSaveGameManager::~idSaveGameManager
========================
*/
idSaveGameManager::~idSaveGameManager() {
processor = NULL;
enumeratedSaveGames.Clear();
}
/*
========================
idSaveGameManager::ExecuteProcessor
========================
*/
saveGameHandle_t idSaveGameManager::ExecuteProcessor( idSaveGameProcessor * processor ) {
idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] : %s\n", __FUNCTION__, processor->Name() );
// may not be running yet, but if we've init'd successfuly, the IsWorking() call should return true if this
// method has been called. You have problems when callees are asking if the processor is done working by using IsWorking()
// the next frame after they've executed the processor.
processor->working = true;
if ( this->processor != NULL ) {
if ( !verify( this->processor != processor ) ) {
idLib::Warning( "[idSaveGameManager::ExecuteProcessor]:1 Someone is trying to execute this processor twice, this is really bad, learn patience padawan!" );
return processor->GetHandle();
} else {
idSaveGameProcessor ** localProcessor = processorQueue.Find( processor );
if ( !verify( localProcessor == NULL ) ) {
idLib::Warning( "[idSaveGameManager::ExecuteProcessor]:2 Someone is trying to execute this processor twice, this is really bad, learn patience padawan!" );
return (*localProcessor)->GetHandle();
}
}
}
processorQueue.Append( processor );
// Don't allow processors to start sub-processors.
// They need to manage their own internal state.
assert( idLib::IsMainThread() );
Sys_InterlockedIncrement( submittedProcessorHandle );
processor->parms.handle = submittedProcessorHandle;
return submittedProcessorHandle;
}
/*
========================
idSaveGameManager::ExecuteProcessorAndWait
========================
*/
saveGameHandle_t idSaveGameManager::ExecuteProcessorAndWait( idSaveGameProcessor * processor ) {
saveGameHandle_t handle = ExecuteProcessor( processor );
if ( handle == 0 ) {
return 0;
}
while ( !IsSaveGameCompletedFromHandle( handle ) ) {
Pump();
Sys_Sleep( 10 );
}
// One more pump to get the completed callback
//Pump();
return handle;
}
/*
========================
idSaveGameManager::WaitForAllProcessors
Since the load & nextMap processors calls execute map change, we can't wait if they are the only ones in the queue
If there are only resettable processors in the queue or no items in the queue, don't wait.
We would need to overrideSimpleProcessorCheck if we were sure we had done something that would cause the processors
to bail out nicely. Something like canceling a disc swap during a loading disc swap dialog...
========================
*/
void idSaveGameManager::WaitForAllProcessors( bool overrideSimpleProcessorCheck ) {
assert( idLib::IsMainThread() );
while ( IsWorking() || ( processorQueue.Num() > 0 ) ) {
if ( !overrideSimpleProcessorCheck ) {
// BEFORE WE WAIT, and potentially hang everything, make sure processors about to be executed won't sit and
// wait for themselves to complete.
// Since we pull off simple processors first, we can stop waiting when the processor being executed is not simple
if ( processor != NULL ) {
if ( !processor->IsSimpleProcessor() ) {
break;
}
} else if ( !processorQueue[0]->IsSimpleProcessor() ) {
break;
}
}
saveThread.WaitForThread();
Pump();
}
}
/*
========================
idSaveGameManager::CancelAllProcessors
========================
*/
void idSaveGameManager::CancelAllProcessors( const bool forceCancelInFlightProcessor ) {
assert( idLib::IsMainThread() );
cancel = true;
if ( forceCancelInFlightProcessor ) {
if ( processor != NULL ) {
processor->GetSignal().Raise();
}
}
Pump(); // must be called from the main thread
Clear();
cancel = false;
}
/*
========================
idSaveGameManager::CancelToTerminate
========================
*/
void idSaveGameManager::CancelToTerminate() {
if ( processor != NULL ) {
processor->parms.cancelled = true;
processor->GetSignal().Raise();
saveThread.WaitForThread();
}
}
/*
========================
idSaveGameManager::DeviceSelectorWaitingOnSaveRetry
========================
*/
bool idSaveGameManager::DeviceSelectorWaitingOnSaveRetry() {
if ( retryFolder == NULL ) {
return false;
}
return ( idStr::Icmp( retryFolder, "GAME-autosave" ) == 0 );
}
/*
========================
idSaveGameManager::Set360RetrySaveAfterDeviceSelected
========================
*/
void idSaveGameManager::Set360RetrySaveAfterDeviceSelected( const char * folder, const int64 bytes ) {
retryFolder = folder;
retryBytes = bytes;
}
/*
========================
idSaveGameManager::ClearRetryInfo
========================
*/
void idSaveGameManager::ClearRetryInfo() {
retryFolder = NULL;
retryBytes = 0;
}
/*
========================
idSaveGameManager::RetrySave
========================
*/
void idSaveGameManager::RetrySave() {
if ( DeviceSelectorWaitingOnSaveRetry() && !common->Dialog().HasDialogMsg( GDM_WARNING_FOR_NEW_DEVICE_ABOUT_TO_LOSE_PROGRESS, false ) ) {
cmdSystem->AppendCommandText( "savegame autosave\n" );
}
}
/*
========================
idSaveGameManager::ShowRetySaveDialog
========================
*/
void idSaveGameManager::ShowRetySaveDialog() {
ShowRetySaveDialog( retryFolder, retryBytes );
}
/*
========================
idSaveGameManager::ShowRetySaveDialog
========================
*/
void idSaveGameManager::ShowRetySaveDialog( const char * folder, const int64 bytes ) {
idStaticList< idSWFScriptFunction *, 4 > callbacks;
idStaticList< idStrId, 4 > optionText;
class idSWFScriptFunction_Continue : public idSWFScriptFunction_RefCounted {
public:
idSWFScriptVar Call( idSWFScriptObject * thisObject, const idSWFParmList & parms ) {
common->Dialog().ClearDialog( GDM_INSUFFICENT_STORAGE_SPACE );
session->GetSaveGameManager().ClearRetryInfo();
return idSWFScriptVar();
}
};
callbacks.Append( new (TAG_SWF) idSWFScriptFunction_Continue() );
optionText.Append( idStrId( "#str_dlg_continue_without_saving" ) );
// build custom space required string
// #str_dlg_space_required ~= "There is insufficient storage available. Please free %s and try again."
idStr format = idStrId( "#str_dlg_space_required" ).GetLocalizedString();
idStr size;
if ( bytes > ( 1024 * 1024 ) ) {
const float roundUp = ( ( 1024.0f * 1024.0f / 10.0f )- 1.0f );
size = va( "%.1f MB", ( roundUp + (float) bytes ) / ( 1024.0f * 1024.0f ) );
} else {
const float roundUp = 1024.0f - 1.0f;
size = va( "%.0f KB", ( roundUp + (float) bytes ) / 1024.0f );
}
idStr msg = va( format.c_str(), size.c_str() );
common->Dialog().AddDynamicDialog( GDM_INSUFFICENT_STORAGE_SPACE, callbacks, optionText, true, msg, true );
}
/*
========================
idSaveGameManager::CancelWithHandle
========================
*/
void idSaveGameManager::CancelWithHandle( const saveGameHandle_t & handle ) {
if ( handle == 0 || IsSaveGameCompletedFromHandle( handle ) ) {
return;
}
// check processor in flight first
if ( processor != NULL ) {
if ( processor->GetHandle() == handle ) {
processor->Cancel();
return;
}
}
// remove from queue
for ( int i = 0; i < processorQueue.Num(); ++i ) {
if ( processorQueue[i]->GetHandle() == handle ) {
processorQueue[i]->Cancel();
return;
}
}
}
/*
========================
idSaveGameManager::StartNextProcessor
Get the next not-reset-capable processor. If there aren't any left, just get what's next.
========================
*/
void idSaveGameManager::StartNextProcessor() {
if ( cancel ) {
return;
}
idSaveGameProcessor * nextProcessor = NULL;
int index = 0;
// pick off the first simple processor
for ( int i = 0; i < processorQueue.Num(); ++i ) {
if ( processorQueue[i]->IsSimpleProcessor() ) {
index = i;
break;
}
}
if ( processorQueue.Num() > 0 ) {
nextProcessor = processorQueue[index];
Sys_InterlockedIncrement( executingProcessorHandle );
processorQueue.RemoveIndex( index );
processor = nextProcessor;
processor->parms.callbackSignal.Raise(); // signal that the thread is ready for work
startTime = Sys_Milliseconds();
}
}
/*
========================
idSaveGameManager::FinishProcessor
========================
*/
void idSaveGameManager::FinishProcessor( idSaveGameProcessor * localProcessor ) {
assert( localProcessor != NULL );
idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] : %s, %d ms\n", __FUNCTION__, localProcessor->Name(), Sys_Milliseconds() - startTime );
// This will delete from the files set for auto-deletion
// Don't remove files not set for auto-deletion, they may be used outside of the savegame manager by game-side callbacks for example
for ( int i = ( localProcessor->parms.files.Num() - 1 ); i >= 0; --i ) {
if ( localProcessor->parms.files[i]->type & SAVEGAMEFILE_AUTO_DELETE ) {
delete localProcessor->parms.files[i];
localProcessor->parms.files.RemoveIndexFast( i );
}
}
localProcessor->init = false;
localProcessor = NULL;
}
/*
========================
idSaveGameManager::Clear
========================
*/
void idSaveGameManager::Clear() {
processorQueue.Clear();
}
/*
========================
idSaveGameManager::IsWorking
========================
*/
bool idSaveGameManager::IsWorking() const {
return processor != NULL;
}
/*
========================
idSaveGameManager::Pump
Important sections called out with -- EXTRA LARGE -- comments!
========================
*/
void idSaveGameManager::Pump() {
// After a processor is done, the next is pulled off the queue so the only way the manager isn't working is if
// there isn't something executing or in the queue.
if ( !IsWorking() ) {
// Unified start to initialize system on PS3 and do appropriate checks for system combination issues
// ------------------------------------
// START
// ------------------------------------
StartNextProcessor();
if ( !IsWorking() ) {
return;
}
continueProcessing = true;
}
if ( cancel ) {
processor->parms.AbortSaveGameFilePipeline();
}
// Quickly checks to see if the savegame thread is done, otherwise, exit and continue frame commands
if ( processor->IsThreadFinished() ) {
idLib::PrintfIf( saveGame_verbose.GetBool(), "%s waited on processor [%s], error = 0x%08X, %s\n", __FUNCTION__, processor->Name(), processor->GetError(), GetSaveGameErrorString( processor->GetError() ).c_str() );
if ( !cancel && continueProcessing ) {
// Check for available storage unit
if ( session->GetSignInManager().GetMasterLocalUser() != NULL ) {
if ( !session->GetSignInManager().GetMasterLocalUser()->IsStorageDeviceAvailable() ) {
// this will not allow further processing
processor->parms.errorCode = SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE;
}
}
// Execute Process() on the processor, if there was an error in a previous Process() call, give the
// processor the chance to validate that error and either clean itself up or convert it to another error or none.
if ( processor->GetError() == SAVEGAME_E_NONE || processor->ValidateLastError() ) {
idLib::PrintfIf( saveGame_verbose.GetBool(), "%s calling %s::Process(), error = 0x%08X, %s\n", __FUNCTION__, processor->Name(), processor->GetError(), GetSaveGameErrorString( processor->GetError() ).c_str() );
// ------------------------------------
// PROCESS
// ------------------------------------
continueProcessing = processor->Process();
// If we don't return here, the completedCallback will be executed before it's done with it's async operation
// during it's last process stage.
return;
} else {
continueProcessing = false;
}
}
// This section does specific post-processing for each of the save commands
if ( !continueProcessing ) {
// Clear out details if we detect corruption but keep directory/slot information
for ( int i = 0; i < processor->parms.detailList.Num(); ++i ) {
idSaveGameDetails & details = processor->parms.detailList[i];
if ( details.damaged ) {
details.descriptors.Clear();
}
}
idLib::PrintfIf( saveGame_verbose.GetBool(), "%s calling %s::CompletedCallback()\n", __FUNCTION__, processor->Name() );
processor->working = false;
// This ensures that the savegame manager will believe the processor is done when there is a potentially
// catastrophic thing that will happen within CompletedCallback which might try to sync all threads
// The most common case of this is executing a map change (which we no longer do).
// We flush the heap and wait for all background processes to finish. After all this is called, we will
// cleanup the old processor within FinishProcessor()
idSaveGameProcessor * localProcessor = processor;
processor = NULL;
// ------------------------------------
// COMPLETEDCALLBACK
// At this point, the handle will be completed
// ------------------------------------
Sys_InterlockedIncrement( lastExecutedProcessorHandle );
for ( int i = 0; i < localProcessor->completedCallbacks.Num(); i++ ) {
localProcessor->completedCallbacks[i]->Call();
}
localProcessor->completedCallbacks.DeleteContents( true );
// ------------------------------------
// FINISHPROCESSOR
// ------------------------------------
FinishProcessor( localProcessor );
}
} else if ( processor->ShouldTimeout() ) {
// Hack for the PS3 threading hang
idLib::PrintfIf( saveGame_verbose.GetBool(), "----- PROCESSOR TIMEOUT ----- (%s)\n", processor->Name() );
idSaveGameProcessor * tempProcessor = processor;
CancelAllProcessors( true );
class idSWFScriptFunction_TryAgain : public idSWFScriptFunction_RefCounted {
public:
idSWFScriptFunction_TryAgain( idSaveGameManager * manager, idSaveGameProcessor * processor ) {
this->manager = manager;
this->processor = processor;
}
idSWFScriptVar Call ( idSWFScriptObject * thisObject, const idSWFParmList & parms ) {
common->Dialog().ClearDialog( GDM_ERROR_SAVING_SAVEGAME );
manager->ExecuteProcessor( processor );
return idSWFScriptVar();
}
private:
idSaveGameManager * manager;
idSaveGameProcessor * processor;
};
idStaticList< idSWFScriptFunction *, 4 > callbacks;
idStaticList< idStrId, 4 > optionText;
callbacks.Append( new (TAG_SWF) idSWFScriptFunction_TryAgain( this, tempProcessor ) );
optionText.Append( idStrId( "#STR_SWF_RETRY" ) );
common->Dialog().AddDynamicDialog( GDM_ERROR_SAVING_SAVEGAME, callbacks, optionText, true, "" );
}
}
+483
View File
@@ -0,0 +1,483 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_SAVEGAME_H__
#define __SYS_SAVEGAME_H__
#ifdef OUTPUT_FUNC
#undef OUTPUT_FUNC
#endif
#ifdef OUTPUT_FUNC_EXIT
#undef OUTPUT_FUNC_EXIT
#endif
#define OUTPUT_FUNC() idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] Enter\n", __FUNCTION__ )
#define OUTPUT_FUNC_EXIT() idLib::PrintfIf( saveGame_verbose.GetBool(), "[%s] Exit\n", __FUNCTION__ )
#define DEFINE_CLASS( x ) virtual const char * Name() const { return #x; }
#define MAX_SAVEGAMES 16
#define MAX_FILES_WITHIN_SAVEGAME 10
#define MIN_SAVEGAME_SIZE_BYTES ( 4 * 1024 * 1024 )
#define MAX_SAVEGAME_STRING_TABLE_SIZE 400 * 1024 // 400 kB max string table size
#define MAX_FILENAME_LENGTH 255
#define MAX_FILENAME_LENGTH_PATTERN 8
#define MAX_FOLDER_NAME_LENGTH 64
#define SAVEGAME_DETAILS_FILENAME "game.details"
// PS3 restrictions: The only characters that can be used are 0-9 (numbers), A-Z (uppercase alphabet), "_" (underscore), and "-" (hyphen)
#define SAVEGAME_AUTOSAVE_FOLDER "AUTOSAVE" // auto save slot
// common descriptors for savegame description fields
#define SAVEGAME_DETAIL_FIELD_EXPANSION "expansion"
#define SAVEGAME_DETAIL_FIELD_MAP "mapName"
#define SAVEGAME_DETAIL_FIELD_MAP_LOCATE "mapLocation"
#define SAVEGAME_DETAIL_FIELD_DIFFICULTY "difficulty"
#define SAVEGAME_DETAIL_FIELD_PLAYTIME "playTime"
#define SAVEGAME_DETAIL_FIELD_LANGUAGE "language"
#define SAVEGAME_DETAIL_FIELD_SAVE_VERSION "saveVersion"
#define SAVEGAME_DETAIL_FIELD_CHECKSUM "checksum"
#define SAVEGAME_GAME_DIRECTORY_PREFIX "GAME-"
#define SAVEGAME_PROFILE_DIRECTORY_PREFIX ""
#define SAVEGAME_RAW_DIRECTORY_PREFIX ""
extern idCVar saveGame_verbose;
extern idCVar saveGame_enable;
class idGameSpawnInfo;
class idSession;
class idSessionLocal;
class idSaveGameManager;
// Specific savegame sub-system errors
enum saveGameError_t {
SAVEGAME_E_NONE = 0,
SAVEGAME_E_CANCELLED = BIT( 0 ),
SAVEGAME_E_INSUFFICIENT_ROOM = BIT( 1 ),
SAVEGAME_E_CORRUPTED = BIT( 2 ),
SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE = BIT( 3 ),
SAVEGAME_E_UNKNOWN = BIT( 4 ),
SAVEGAME_E_INVALID_FILENAME = BIT( 5 ),
SAVEGAME_E_STEAM_ERROR = BIT( 6 ),
SAVEGAME_E_FOLDER_NOT_FOUND = BIT( 7 ),
SAVEGAME_E_FILE_NOT_FOUND = BIT( 8 ),
SAVEGAME_E_DLC_NOT_FOUND = BIT( 9 ),
SAVEGAME_E_INVALID_USER = BIT( 10 ),
SAVEGAME_E_PROFILE_TOO_BIG = BIT( 11 ),
SAVEGAME_E_DISC_SWAP = BIT( 12 ),
SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION = BIT( 13 ),
SAVEGAME_E_BITS_USED = 14,
SAVEGAME_E_NUM = SAVEGAME_E_BITS_USED + 1 // because we're counting "none"
};
// Modes to control behavior of savegame manager
enum saveGameModeBitfield_t {
SAVEGAME_MBF_NONE = 0,
SAVEGAME_MBF_LOAD = BIT( 0 ), // standard file load (can be individual/multiple files described in parms)
SAVEGAME_MBF_SAVE = BIT( 1 ), // standard file save (can be individual/multiple files described in parms)
SAVEGAME_MBF_DELETE_FOLDER = BIT( 2 ), // standard package delete
SAVEGAME_MBF_DELETE_ALL_FOLDERS = BIT( 3 ), // deletes all of the savegame folders (should only be used in testing)
SAVEGAME_MBF_ENUMERATE = BIT( 4 ), // gets listing of all savegame folders, typically used with READ_DETAILS to read the description file
SAVEGAME_MBF_NO_COMPRESS = BIT( 5 ), // tells the system the files aren't compressed, usually only needed when reading the descriptors file internally
SAVEGAME_MBF_ENUMERATE_FILES = BIT( 6 ), // enumerates all the files within a particular savegame folder (can be individual/multiple files or pattern described in parms)
SAVEGAME_MBF_DELETE_FILES = BIT( 7 ), // deletes individual files within a particular savegame folder (can be individual/multiple files or pattern described in parms)
SAVEGAME_MBF_READ_DETAILS = BIT( 8 ), // reads the description file (if specified, parms.enumeratedEntry.name & parms.enumeratedEntry.type must be specified)
SAVEGAME_MBF_KEEP_FOLDER = BIT( 9 ) // don't delete the folder before saving
};
typedef interlockedInt_t saveGameHandle_t;
typedef int savegameUserId_t; // [internal] hash of gamer tag for steam
/*
================================================
saveGameCheck_t
================================================
*/
struct saveGameCheck_t {
saveGameCheck_t() {
exists = false;
autosaveExists = false;
autosaveFolder = NULL;
}
bool exists;
bool autosaveExists;
const char * autosaveFolder;
};
/*
================================================
idSaveGameDetails
================================================
*/
class idSaveGameDetails {
public:
idSaveGameDetails();
~idSaveGameDetails() { Clear(); }
void Clear();
bool operator==( const idSaveGameDetails & other ) const { return ( idStr::Icmp( slotName, other.slotName ) == 0 ); }
idSaveGameDetails & operator=( const idSaveGameDetails &other ) {
descriptors.Clear();
descriptors = other.descriptors;
damaged = other.damaged;
date = other.date;
slotName = other.slotName;
return *this;
}
// for std::sort, sort newer (larger date) towards start of list
bool operator<( const idSaveGameDetails & other ) { return date > other.date; }
idStr GetMapName() const { return descriptors.GetString( SAVEGAME_DETAIL_FIELD_MAP, "" ); }
idStr GetLocation() const { return descriptors.GetString( SAVEGAME_DETAIL_FIELD_MAP_LOCATE, "" ); }
idStr GetLanguage() const { return descriptors.GetString( SAVEGAME_DETAIL_FIELD_LANGUAGE, "" ); }
int GetPlaytime() const { return descriptors.GetInt( SAVEGAME_DETAIL_FIELD_PLAYTIME, 0 ); }
int GetExpansion() const { return descriptors.GetInt( SAVEGAME_DETAIL_FIELD_EXPANSION, 0 ); }
int GetDifficulty() const { return descriptors.GetInt( SAVEGAME_DETAIL_FIELD_DIFFICULTY, -1 ); }
int GetSaveVersion() const { return descriptors.GetInt( SAVEGAME_DETAIL_FIELD_SAVE_VERSION, 0 ); }
public:
idDict descriptors; // [in] Descriptors available to be shown on the save/load screen. Each game can define their own, e.g. Difficulty, level, map, score, time.
bool damaged; // [out]
time_t date; // [out] read from the filesystem, not set by client
idStrStatic< MAX_FOLDER_NAME_LENGTH > slotName; // [out] folder/slot name, e.g. AUTOSAVE
};
typedef idStaticList< idSaveGameDetails, MAX_SAVEGAMES > saveGameDetailsList_t;
// Making a auto_ptr to handle lifetime issues better
typedef idList< idFile_SaveGame *, TAG_SAVEGAMES > saveFileEntryList_t;
/*
================================================
idSaveLoadParms
================================================
*/
class idSaveLoadParms {
public:
idSaveLoadParms();
~idSaveLoadParms();
void ResetCancelled();
void Init();
void SetDefaults( int inputDevice = -1 ); // doesn't clear out things that should be persistent across entire processor
void CancelSaveGameFilePipelines();
void AbortSaveGameFilePipeline();
const int & GetError() const { return errorCode; }
const int & GetHandledErrors() const { return handledErrorCodes; }
const saveGameHandle_t & GetHandle() const { return handle; }
public:
idStrStatic< MAX_FOLDER_NAME_LENGTH > directory; // [in] real directory of the savegame package
idStrStatic< MAX_FILENAME_LENGTH_PATTERN > pattern; // [in] pattern to use while enumerating/deleting files within a savegame folder
idStrStatic< MAX_FILENAME_LENGTH_PATTERN > postPattern; // [in] pattern at the end of the file to use while enumerating/deleting files within a savegame folder
int mode; // [in] SAVE, LOAD, ENUM, DELETE, etc.
idSaveGameDetails description; // [in/out] in: description used to serialize into game.details file, out: if SAVEGAME_MBF_READ_DETAILS used with certain modes, item 0 contains the read details
saveFileEntryList_t files; // [in/out] in: files to be saved, out: objects loaded, for SAVEGAME_MBF_ENUMERATE_FILES, it contains a listing of the filenames only
saveGameDetailsList_t detailList; // [out] listing of the enumerated savegames used only with SAVEGAME_MBF_ENUMERATE
int errorCode; // [out] combination of saveGameError_t bits
int handledErrorCodes; // [out] combination of saveGameError_t bits
int64 requiredSpaceInBytes; // [out] when fails for insufficient space, this is populated with additional space required
int skipErrorDialogMask;
// ----------------------
// Internal vars
// ----------------------
idSysSignal callbackSignal; // [internal] used to signal savegame manager that the Process() call is completed (we still might have more Process() calls to make though...)
volatile bool cancelled; // [internal] while processor is running, this can be set outside of the normal operation of the processor. Each implementation should check this during operation to allow it to shutdown cleanly.
savegameUserId_t userId; // [internal] to get the proper user during every step
int inputDeviceId; // [internal] consoles will use this to segregate each player's files
saveGameHandle_t handle;
private:
// Don't allow copies
idSaveLoadParms( const idSaveLoadParms & s ) {}
void operator=( const idSaveLoadParms & s ) {}
};
// Using function pointers because:
// 1. CompletedCallback methods in processors weren't generic enough, we could use SaveFiles processors
// for profiles/games, but there would be a single completed callback and we'd have to update
// the callback to detect what type of call it was, store the type in the processor, etc.
// 2. Using a functor class would require us to define classes for each callback. The definition of those
// classes could be scattered and a little difficult to follow
// 3. With callback methods, we assign them when needed and know exactly where they are defined/declared.
//typedef void (*saveGameProcessorCallback_t)( idSaveLoadParms & parms );
/*
================================================
saveGameThreadArgs_t
================================================
*/
struct saveGameThreadArgs_t {
saveGameThreadArgs_t() :
saveLoadParms( NULL ) {
}
idSaveLoadParms * saveLoadParms;
};
/*
================================================
idSaveGameThread
================================================
*/
class idSaveGameThread : public idSysThread {
public:
idSaveGameThread() : cancel( false ) {}
int Run();
void CancelOperations() { cancel = true; }
private:
int Save();
int Load();
int Enumerate();
int Delete();
int DeleteAll();
int DeleteFiles();
int EnumerateFiles();
public:
saveGameThreadArgs_t data;
volatile bool cancel;
};
/*
================================================
idSaveGameProcessor
================================================
*/
class idSaveGameProcessor {
friend class idSaveGameManager;
public:
DEFINE_CLASS( idSaveGameProcessor );
static const int MAX_COMPLETED_CALLBACKS = 5;
idSaveGameProcessor();
virtual ~idSaveGameProcessor() { }
//------------------------
// Virtuals
//------------------------
// Basic init
virtual bool Init();
// This method should returns true if the processor has additional sub-states to
// manage. The saveGameManager will retain the current state and Process() will be called again. When this method
// returns false Process() will not be called again. For example, during save, you might want to load other files
// and save them somewhere else, return true until you are done with the entire state.
virtual bool Process() { return false; }
// Gives each processor to validate an error returned from the previous process call.
// This is useful when processors have a multi-stage Process() and expect some benign errors like
// deleting a savegame folder before copying into it.
virtual bool ValidateLastError() { return false; }
// Processors need to override this if they will eventually reset the map.
// If it could possibly reset the map through any of its stages, including kicking off another processor in completed callback, return false.
// We will force non-simple processors to execute last and won't block the map heap reset due if non-simple processors are still executing.
virtual bool IsSimpleProcessor() const { return true; }
// This is a fail-safe to catch a timing issue on the PS3 where the nextmap processor could sometimes hang during a level transition
virtual bool ShouldTimeout() const { return false; }
//------------------------
// Commands
//------------------------
// Cancels this processor in whatever state it's currently in and sets an error code for SAVEGAME_E_CANCELLED
void Cancel() { parms.cancelled = true; parms.errorCode = SAVEGAME_E_CANCELLED; }
//------------------------
// Accessors
//------------------------
// Returns error status
idSysSignal & GetSignal() { return parms.callbackSignal; }
// Returns error status
const int & GetError() const { return parms.errorCode; }
// Returns the processor's save/load parms
const idSaveLoadParms & GetParms() const { return parms; }
// Returns the processor's save/load parms
idSaveLoadParms & GetParmsNonConst() { return parms; }
// Returns if this processor is currently working
bool IsWorking() const { return working; }
// This is a way to tell the processor which errors shouldn't be handled by the processor or system.
void SetSkipSystemErrorDialogMask( const int errorMask ) { parms.skipErrorDialogMask = errorMask; }
int GetSkipSystemErrorDialogMask() const { return parms.skipErrorDialogMask; }
// Returns the handle given by execution
saveGameHandle_t GetHandle() const { return parms.GetHandle(); }
// These can be overridden by game code, like the GUI, when the processor is done executing.
// Game classes like the GUI can create a processor derived from a game's Save processor impl and simply use
// this method to know when everything is done. It eases the burden of constantly checking the working flag.
// This will be called back within the game thread during SaveGameManager::Pump().
void AddCompletedCallback( const idCallback & callback );
private:
// Returns whether or not the thread is finished operating, should only be called by the savegame manager
bool IsThreadFinished();
protected:
idSaveLoadParms parms;
int savegameLogicTestIterator;
private:
bool init;
bool working;
idStaticList< idCallback *, MAX_COMPLETED_CALLBACKS > completedCallbacks;
};
/*
================================================
idSaveGameManager
Why all the object-oriented nonsense?
- Savegames need to be processed asynchronously, saving/loading/deleting files should happen during the game frame
so there is a common way to update the render device.
- When executing commands, if no "strategy"s are used, the pump() method would need to have a switch statement,
extending the manager for other commands would mean modifying the manager itself for various commands.
By making it a strategy, we are able to create custom commands and define the behavior within game code and keep
the manager code in the engine static.
================================================
*/
class idSaveGameManager {
public:
enum packageType_t {
PACKAGE_PROFILE,
PACKAGE_GAME,
PACKAGE_RAW,
PACKAGE_NUM
};
const static int MAX_SAVEGAME_DIRECTORY_DEPTH = 5;
explicit idSaveGameManager();
~idSaveGameManager();
// Called within main game thread
void Pump();
// Has the storage device been selected yet? This is only an issue on the 360, and primarily for development purposes
bool IsStorageAvailable() const { return storageAvailable; }
void SetStorageAvailable( const bool available ) { storageAvailable = available; }
// Check to see if a processor is set within the manager
bool IsWorking() const;
// Assign a processor to the manager. The processor should belong in game-side code
// This queues up processors and executes them serially
// Returns whether or not the processor is immediately executed
saveGameHandle_t ExecuteProcessor( idSaveGameProcessor * processor );
// Synchronous version, CompletedCallback is NOT called.
saveGameHandle_t ExecuteProcessorAndWait( idSaveGameProcessor * processor );
// Lets the currently processing queue finish, but clears the processor queue
void Clear();
void WaitForAllProcessors( bool overrideSimpleProcessorCheck = false );
const bool IsCancelled() const { return cancel; }
void CancelAllProcessors( const bool forceCancelInFlightProcessor );
void CancelToTerminate();
idSaveGameThread & GetSaveGameThread() { return saveThread; }
bool IsSaveGameCompletedFromHandle( const saveGameHandle_t & handle ) const { return handle <= lastExecutedProcessorHandle || handle == 0; } // last case should never be reached since it would be also be true in first case, this is just to show intent
void Set360RetrySaveAfterDeviceSelected( const char * folder, const int64 bytes );
bool DeviceSelectorWaitingOnSaveRetry();
void ShowRetySaveDialog( const char * folder, const int64 bytes );
void ShowRetySaveDialog();
void ClearRetryInfo();
void RetrySave();
// This will cause the processor to cancel execution, the completion callback will be called
void CancelWithHandle( const saveGameHandle_t & handle );
const saveGameDetailsList_t & GetEnumeratedSavegames() const { return enumeratedSaveGames; }
saveGameDetailsList_t & GetEnumeratedSavegamesNonConst() { return enumeratedSaveGames; }
private:
// These are to make sure that all processors start and finish in the same way without a lot of code duplication.
// We need to make sure that we adhere to PS3 system combination initialization issues.
void StartNextProcessor();
void FinishProcessor( idSaveGameProcessor * processor );
// Calls start on the processor after it's been assigned
void Start();
private:
idSaveGameProcessor * processor;
idStaticList< idSaveGameProcessor *, 4 > processorQueue;
bool cancel;
idSaveGameThread saveThread;
int startTime;
bool continueProcessing;
saveGameHandle_t submittedProcessorHandle;
saveGameHandle_t executingProcessorHandle;
saveGameHandle_t lastExecutedProcessorHandle;
saveGameDetailsList_t enumeratedSaveGames;
bool storageAvailable; // On 360, this is false by default, after the storage device is selected
// it becomes true. This allows us to start the game without a storage device
// selected and pop the selector when necessary.
const char * retryFolder;
int64 retryBytes;
bool retrySave;
idSysSignal deviceRequestedSignal;
};
// Bridge between the session's APIs and the savegame thread
void Sys_ExecuteSavegameCommandAsync( idSaveLoadParms * savegameParms );
// Folder prefix should be NULL for everything except PS3
// Synchronous check, just checks if any savegame exists for master local user and if one is an autosave
void Sys_SaveGameCheck( bool & exists, bool & autosaveExists );
const idStr & GetSaveFolder( idSaveGameManager::packageType_t type );
idStr AddSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type );
idStr RemoveSaveFolderPrefix( const char * folder, idSaveGameManager::packageType_t type );
bool SavegameReadDetailsFromFile( idFile * file, idSaveGameDetails & details );
idStr GetSaveGameErrorString( int errorMask );
#endif // __SYS_SAVEGAME_H__
+643
View File
@@ -0,0 +1,643 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_SESSION_H__
#define __SYS_SESSION_H__
#include "../framework/Serializer.h"
#include "sys_localuser.h"
typedef uint8 peerMask_t;
static const int MAX_PLAYERS = 8;
static const int MAX_REDUNDANT_CMDS = 3;
static const int MAX_LOCAL_PLAYERS = 2;
static const int MAX_INPUT_DEVICES = 4;
enum matchFlags_t {
MATCH_STATS = BIT( 0 ), // Match will upload leaderboard/achievement scores
MATCH_ONLINE = BIT( 1 ), // Match will require users to be online
MATCH_RANKED = BIT( 2 ), // Match will affect rank
MATCH_PRIVATE = BIT( 3 ), // Match will NOT be searchable through FindOrCreateMatch
MATCH_INVITE_ONLY = BIT( 4 ), // Match visible through invite only
MATCH_REQUIRE_PARTY_LOBBY = BIT( 5 ), // This session uses a party lobby
MATCH_PARTY_INVITE_PLACEHOLDER = BIT( 6 ), // Party is never shown in the UI, it's simply used as a placeholder for invites
MATCH_JOIN_IN_PROGRESS = BIT( 7 ), // Join in progress supported for this match
};
ID_INLINE bool MatchTypeIsOnline( uint8 matchFlags ) { return ( matchFlags & MATCH_ONLINE ) ? true : false; }
ID_INLINE bool MatchTypeIsLocal( uint8 matchFlags ) { return !MatchTypeIsOnline( matchFlags ); }
ID_INLINE bool MatchTypeIsPrivate( uint8 matchFlags ) { return ( matchFlags & MATCH_PRIVATE ) ? true : false; }
ID_INLINE bool MatchTypeIsRanked( uint8 matchFlags ) { return ( matchFlags & MATCH_RANKED ) ? true : false; }
ID_INLINE bool MatchTypeHasStats( uint8 matchFlags ) { return ( matchFlags & MATCH_STATS ) ? true : false; }
ID_INLINE bool MatchTypeInviteOnly( uint8 matchFlags ) { return ( matchFlags & MATCH_INVITE_ONLY ) ? true : false; }
ID_INLINE bool MatchTypeIsSearchable( uint8 matchFlags ) { return !MatchTypeIsPrivate( matchFlags ); }
ID_INLINE bool MatchTypeIsJoinInProgress( uint8 matchFlags ){ return ( matchFlags & MATCH_JOIN_IN_PROGRESS ) ? true : false; }
class idCompressor;
class idLeaderboardSubmission;
class idLeaderboardQuery;
class idSignInManagerBase;
class idPlayerProfile;
class idGameSpawnInfo;
class idSaveLoadParms;
class idSaveGameManager;
class idLocalUser;
class idDedicatedServerSearch;
class idAchievementSystem;
class idLeaderboardCallback;
struct leaderboardDefinition_t;
struct column_t;
const int8 GAME_MODE_RANDOM = -1;
const int8 GAME_MODE_SINGLEPLAYER = -2;
const int8 GAME_MAP_RANDOM = -1;
const int8 GAME_MAP_SINGLEPLAYER = -2;
const int8 GAME_EPISODE_UNKNOWN = -1;
const int8 GAME_SKILL_DEFAULT = -1;
const int DefaultPartyFlags = MATCH_JOIN_IN_PROGRESS | MATCH_ONLINE;
const int DefaultPublicGameFlags = MATCH_JOIN_IN_PROGRESS | MATCH_REQUIRE_PARTY_LOBBY | MATCH_RANKED | MATCH_STATS;
const int DefaultPrivateGameFlags = MATCH_JOIN_IN_PROGRESS | MATCH_REQUIRE_PARTY_LOBBY | MATCH_PRIVATE;
/*
================================================
idMatchParameters
================================================
*/
class idMatchParameters {
public:
idMatchParameters() :
numSlots( MAX_PLAYERS ),
gameMode( GAME_MODE_RANDOM ),
gameMap( GAME_MAP_RANDOM ),
gameEpisode( GAME_EPISODE_UNKNOWN ),
gameSkill( GAME_SKILL_DEFAULT ),
matchFlags( 0 )
{}
void Write( idBitMsg & msg ) { idSerializer s( msg, true ); Serialize( s ); }
void Read( idBitMsg & msg ) { idSerializer s( msg, false ); Serialize( s ); }
void Serialize( idSerializer & serializer ) {
serializer.Serialize( gameMode );
serializer.Serialize( gameMap );
serializer.Serialize( gameEpisode );
serializer.Serialize( gameSkill );
serializer.Serialize( numSlots );
serializer.Serialize( matchFlags );
serializer.SerializeString( mapName );
serverInfo.Serialize( serializer );
}
uint8 numSlots;
int8 gameMode;
int8 gameMap;
int8 gameEpisode; // Episode for doom classic support.
int8 gameSkill; // Skill for doom classic support.
uint8 matchFlags;
idStr mapName; // This is only used for SP (gameMap == GAME_MAP_SINGLEPLAYER)
idDict serverInfo;
};
/*
================================================
serverInfo_t
Results from calling ListServers/ServerInfo for game browser / system link.
================================================
*/
struct serverInfo_t {
serverInfo_t() :
gameMode( GAME_MODE_RANDOM ),
gameMap( GAME_MAP_RANDOM ),
joinable(),
numPlayers(),
maxPlayers()
{}
void Write( idBitMsg & msg ) { idSerializer s( msg, true ); Serialize( s ); }
void Read( idBitMsg & msg ) { idSerializer s( msg, false ); Serialize( s ); }
void Serialize( idSerializer & serializer ) {
serializer.SerializeString( serverName );
serializer.Serialize( gameMode );
serializer.Serialize( gameMap );
SERIALIZE_BOOL( serializer, joinable );
serializer.Serialize( numPlayers );
serializer.Serialize( maxPlayers );
}
idStr serverName;
int8 gameMode;
int8 gameMap;
bool joinable;
int numPlayers;
int maxPlayers;
};
//------------------------
// voiceState_t
//------------------------
enum voiceState_t {
VOICECHAT_STATE_NO_MIC,
VOICECHAT_STATE_MUTED_LOCAL,
VOICECHAT_STATE_MUTED_REMOTE,
VOICECHAT_STATE_MUTED_ALL,
VOICECHAT_STATE_NOT_TALKING,
VOICECHAT_STATE_TALKING,
VOICECHAT_STATE_TALKING_GLOBAL,
NUM_VOICECHAT_STATE
};
//------------------------
// voiceStateDisplay_t
//------------------------
enum voiceStateDisplay_t {
VOICECHAT_DISPLAY_NONE,
VOICECHAT_DISPLAY_NOTTALKING,
VOICECHAT_DISPLAY_TALKING,
VOICECHAT_DISPLAY_TALKING_GLOBAL,
VOICECHAT_DISPLAY_MUTED,
VOICECHAT_DISPLAY_MAX
};
static const int QOS_RESULT_CRAPPY = 200;
static const int QOS_RESULT_WEAK = 100;
static const int QOS_RESULT_GOOD = 50;
static const int QOS_RESULT_GREAT = 0;
//------------------------
// qosState_t
//------------------------
enum qosState_t {
QOS_STATE_CRAPPY = 1,
QOS_STATE_WEAK,
QOS_STATE_GOOD,
QOS_STATE_GREAT,
QOS_STATE_MAX,
};
//------------------------
// leaderboardDisplayError_t
//------------------------
enum leaderboardDisplayError_t {
LEADERBOARD_DISPLAY_ERROR_NONE,
LEADERBOARD_DISPLAY_ERROR_FAILED, // General error occurred
LEADERBOARD_DISPLAY_ERROR_NOT_ONLINE, // No longer online
LEADERBOARD_DISPLAY_ERROR_NOT_RANKED, // Attempting to view a "My Score" leaderboard you aren't ranked on
LEADERBOARD_DISPLAY_ERROR_MAX
};
/*
================================================
lobbyUserID_t
================================================
*/
struct lobbyUserID_t {
public:
lobbyUserID_t() : lobbyType( 0xFF ) {}
explicit lobbyUserID_t( localUserHandle_t localUser_, byte lobbyType_ ) : localUserHandle( localUser_ ), lobbyType( lobbyType_ ) {}
bool operator == ( const lobbyUserID_t & other ) const {
return localUserHandle == other.localUserHandle && lobbyType == other.lobbyType; // Lobby type must match
}
bool operator != ( const lobbyUserID_t & other ) const {
return !( *this == other );
}
bool operator < ( const lobbyUserID_t & other ) const {
if ( localUserHandle == other.localUserHandle ) {
return lobbyType < other.lobbyType; // Lobby type tie breaker
}
return localUserHandle < other.localUserHandle;
}
bool CompareIgnoreLobbyType( const lobbyUserID_t & other ) const {
return localUserHandle == other.localUserHandle;
}
localUserHandle_t GetLocalUserHandle() const { return localUserHandle; }
byte GetLobbyType() const { return lobbyType; }
bool IsValid() const { return localUserHandle.IsValid() && lobbyType != 0xFF; }
void WriteToMsg( idBitMsg & msg ) {
localUserHandle.WriteToMsg( msg );
msg.WriteByte( lobbyType );
}
void ReadFromMsg( const idBitMsg & msg ) {
localUserHandle.ReadFromMsg( msg );
lobbyType = msg.ReadByte();
}
void Serialize( idSerializer & ser );
private:
localUserHandle_t localUserHandle;
byte lobbyType;
};
/*
================================================
idLobbyBase
================================================
*/
class idLobbyBase {
public:
// General lobby functionality
virtual bool IsHost() const = 0;
virtual bool IsPeer() const = 0;
virtual bool HasActivePeers() const = 0;
virtual int GetNumLobbyUsers() const = 0;
virtual int GetNumActiveLobbyUsers() const = 0;
virtual bool IsLobbyUserConnected( int index ) const = 0;
virtual lobbyUserID_t GetLobbyUserIdByOrdinal( int userIndex ) const = 0;
virtual int GetLobbyUserIndexFromLobbyUserID( lobbyUserID_t lobbyUserID ) const = 0;
virtual void SendReliable( int type, idBitMsg & msg, bool callReceiveReliable = true, peerMask_t sessionUserMask = MAX_UNSIGNED_TYPE( peerMask_t ) ) = 0;
virtual void SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg & msg ) = 0;
virtual void SendReliableToHost( int type, idBitMsg & msg ) = 0;
// Lobby user access
virtual const char * GetLobbyUserName( lobbyUserID_t lobbyUserID ) const = 0;
virtual void KickLobbyUser( lobbyUserID_t lobbyUserID ) = 0;
virtual bool IsLobbyUserValid( lobbyUserID_t lobbyUserID ) const = 0;
virtual bool IsLobbyUserLoaded( lobbyUserID_t lobbyUserID ) const = 0;
virtual bool LobbyUserHasFirstFullSnap( lobbyUserID_t lobbyUserID ) const = 0;
virtual void EnableSnapshotsForLobbyUser( lobbyUserID_t lobbyUserID ) = 0;
virtual int GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const = 0;
virtual bool GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const = 0;
virtual bool GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const = 0;
virtual int GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const = 0;
virtual int GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const = 0;
virtual int GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const = 0;
virtual bool SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber ) = 0;
virtual int GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const = 0;
virtual idPlayerProfile * GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID ) = 0;
virtual idLocalUser * GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID ) = 0;
virtual int GetNumLobbyUsersOnTeam( int teamNumber ) const = 0;
virtual int PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const = 0;
virtual int GetPeerTimeSinceLastPacket( int peerIndex ) const = 0;
virtual int PeerIndexForHost() const = 0;
virtual lobbyUserID_t AllocLobbyUserSlotForBot( const char * botName ) = 0;
virtual void RemoveBotFromLobbyUserList( lobbyUserID_t lobbyUserID ) = 0;
virtual bool GetLobbyUserIsBot( lobbyUserID_t lobbyUserID ) const = 0;
virtual const char * GetHostUserName() const = 0;
virtual const idMatchParameters & GetMatchParms() const = 0;
virtual bool IsLobbyFull() const = 0;
// Peer access
virtual bool EnsureAllPeersHaveBaseState() = 0;
virtual bool AllPeersInGame() const = 0;
virtual int GetNumConnectedPeers() const = 0;
virtual int GetNumConnectedPeersInGame() const = 0;
virtual int PeerIndexOnHost() const = 0;
virtual bool IsPeerDisconnected( int peerIndex ) const = 0;
// Snapshots
virtual bool AllPeersHaveStaleSnapObj( int objId ) = 0;
virtual bool AllPeersHaveExpectedSnapObj( int objId ) = 0;
virtual void RefreshSnapObj( int objId ) = 0;
virtual void MarkSnapObjDeleted( int objId ) = 0;
virtual void AddSnapObjTemplate( int objID, idBitMsg & msg ) = 0;
// Debugging
virtual void DrawDebugNetworkHUD() const = 0;
virtual void DrawDebugNetworkHUD2() const = 0;
virtual void DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw ) = 0;
};
/*
================================================
idSession
================================================
*/
class idSession {
public:
enum sessionState_t {
PRESS_START,
IDLE,
SEARCHING,
CONNECTING,
PARTY_LOBBY,
GAME_LOBBY,
LOADING,
INGAME,
BUSY,
MAX_STATES
};
enum sessionOption_t {
OPTION_LEAVE_WITH_PARTY = BIT( 0 ), // As a party leader, whether or not to drag your party members with you when you leave a game lobby
OPTION_ALL = 0xFFFFFFFF
};
idSession() :
signInManager( NULL ),
saveGameManager( NULL ),
achievementSystem( NULL ),
dedicatedServerSearch( NULL ) { }
virtual ~idSession();
virtual void Initialize() = 0;
virtual void Shutdown() = 0;
virtual void InitializeSoundRelatedSystems() = 0;
virtual void ShutdownSoundRelatedSystems() = 0;
//=====================================================================================================
// Lobby management
//=====================================================================================================
virtual void CreatePartyLobby( const idMatchParameters & parms_ ) = 0;
virtual void FindOrCreateMatch( const idMatchParameters & parms_ ) = 0;
virtual void CreateMatch( const idMatchParameters & parms_ ) = 0;
virtual void CreateGameStateLobby( const idMatchParameters & parms_ ) = 0;
virtual void UpdateMatchParms( const idMatchParameters & parms_ ) = 0;
virtual void UpdatePartyParms( const idMatchParameters & parms_ ) = 0;
virtual void StartMatch() = 0;
virtual void EndMatch( bool premature=false ) = 0; // Meant for host to end match gracefully, go back to lobby, tally scores, etc
virtual void MatchFinished() = 0; // this is for when the game is over before we go back to lobby. Need this incase the host leaves during this time
virtual void QuitMatch() = 0; // Meant for host or peer to quit the match before it ends, will instigate host migration, etc
virtual void QuitMatchToTitle() = 0; // Will forcefully quit the match and return to the title screen.
virtual void SetSessionOption( sessionOption_t option ) = 0;
virtual void ClearSessionOption( sessionOption_t option ) = 0;
virtual sessionState_t GetBackState() = 0;
virtual void Cancel() = 0;
virtual void MoveToPressStart() = 0;
virtual void FinishDisconnect() = 0;
virtual void LoadingFinished() = 0;
virtual bool IsCurrentLobbyMigrating() const = 0;
virtual bool IsLosingConnectionToHost() const = 0;
virtual bool WasMigrationGame() const = 0;
virtual bool ShouldRelaunchMigrationGame() const = 0;
virtual bool WasGameLobbyCoalesced() const = 0;
virtual bool GetMigrationGameData( idBitMsg & msg, bool reading ) = 0;
virtual bool GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg & msg, bool reading ) = 0;
virtual bool GetMatchParamUpdate( int & peer, int & msg ) = 0;
virtual void Pump() = 0;
virtual void ProcessSnapAckQueue() = 0;
virtual void InviteFriends() = 0;
virtual void InviteParty() = 0;
virtual void ShowPartySessions() = 0;
virtual bool IsPlatformPartyInLobby() = 0;
// Lobby user/peer access
// The party and game lobby are the two platform lobbies that notify the backends (Steam/PSN/LIVE of changes)
virtual idLobbyBase & GetPartyLobbyBase() = 0;
virtual idLobbyBase & GetGameLobbyBase() = 0;
// Game state lobby is the lobby used while in-game. It is so the dedicated server can host this lobby
// and have all platform clients join. It does NOT notify the backends of changes, it's purely for the dedicated
// server to be able to host the in-game lobby.
virtual idLobbyBase & GetActingGameStateLobbyBase() = 0;
// GetActivePlatformLobbyBase will return either the game or party lobby, it won't return the game state lobby
// This function is generally used for menus, in-game code should refer to GetActingGameStateLobby
virtual idLobbyBase & GetActivePlatformLobbyBase() = 0;
virtual idLobbyBase & GetLobbyFromLobbyUserID( lobbyUserID_t lobbyUserID ) = 0;
virtual idPlayerProfile * GetProfileFromMasterLocalUser() = 0;
virtual bool ProcessInputEvent( const sysEvent_t * ev ) = 0;
virtual float GetUpstreamDropRate() = 0;
virtual float GetUpstreamQueueRate() = 0;
virtual int GetQueuedBytes() = 0;
virtual int GetLoadingID() = 0;
virtual bool IsAboutToLoad() const = 0;
virtual const char * GetLocalUserName( int i ) const = 0;
virtual sessionState_t GetState() const = 0;
virtual const char * GetStateString() const = 0;
virtual int NumServers() const = 0;
virtual void ListServers( const idCallback & callback ) = 0;
virtual void CancelListServers() = 0;
virtual void ConnectToServer( int i ) = 0;
virtual const serverInfo_t * ServerInfo( int i ) const = 0;
virtual const idList< idStr > * ServerPlayerList( int i ) = 0;
virtual void ShowServerGamerCardUI( int i ) = 0;
virtual void ShowOnlineSignin() = 0;
virtual void DropClient( int peerNum, int lobbyType ) = 0;
virtual void JoinAfterSwap( void * joinID ) = 0;
//=====================================================================================================
// Downloadable Content
//=====================================================================================================
virtual void EnumerateDownloadableContent() = 0;
virtual int GetNumContentPackages() const = 0;
virtual int GetContentPackageID( int contentIndex ) const = 0;
virtual const char * GetContentPackagePath( int contentIndex ) const = 0;
virtual int GetContentPackageIndexForID( int contentID ) const = 0;
virtual void ShowSystemMarketplaceUI() const = 0;
virtual bool GetSystemMarketplaceHasNewContent() const = 0;
virtual void SetSystemMarketplaceHasNewContent( bool hasNewContent ) = 0;
//=====================================================================================================
// Title Storage Vars
//=====================================================================================================
virtual float GetTitleStorageFloat( const char * name, float defaultFloat ) const = 0;
virtual int GetTitleStorageInt( const char * name, int defaultInt ) const = 0;
virtual bool GetTitleStorageBool( const char * name, bool defaultBool ) const = 0;
virtual const char * GetTitleStorageString( const char * name, const char * defaultString ) const = 0;
virtual bool GetTitleStorageFloat( const char * name, float defaultFloat, float & out ) const { out = defaultFloat; return false; }
virtual bool GetTitleStorageInt( const char * name, int defaultInt, int & out ) const { out = defaultInt; return false; }
virtual bool GetTitleStorageBool( const char * name, bool defaultBool, bool & out ) const { out = defaultBool; return false; }
virtual bool GetTitleStorageString( const char * name, const char * defaultString, const char ** out ) const { if ( out != NULL ) { *out = defaultString; } return false; }
virtual bool IsTitleStorageLoaded() = 0;
//=====================================================================================================
// Leaderboard
//=====================================================================================================
virtual void LeaderboardUpload( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats, const idFile_Memory * attachment = NULL ) = 0;
virtual void LeaderboardDownload( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int startingRank, int numRows, const idLeaderboardCallback & callback ) = 0;
virtual void LeaderboardDownloadAttachment( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int64 attachmentID ) = 0;
virtual void LeaderboardFlush() = 0;
//=====================================================================================================
// Scoring (currently just for TrueSkill)
//=====================================================================================================
virtual void SetLobbyUserRelativeScore( lobbyUserID_t lobbyUserID, int relativeScore, int team ) = 0;
//=====================================================================================================
// Savegames
//
// Default async implementations, saves to a folder, uses game.details file to describe each save.
// Files saved are up to the game and provide through a callback mechanism. If you want to be notified when
// one of these operations have completed, either modify the framework's completedCallback of each of the
// savegame processors or create your own processors and execute with the savegameManager.
//=====================================================================================================
virtual saveGameHandle_t SaveGameSync( const char * name, const saveFileEntryList_t & files, const idSaveGameDetails & description ) = 0;
virtual saveGameHandle_t SaveGameAsync( const char * name, const saveFileEntryList_t & files, const idSaveGameDetails & description ) = 0;
virtual saveGameHandle_t LoadGameSync( const char * name, saveFileEntryList_t & files ) = 0;
virtual saveGameHandle_t EnumerateSaveGamesSync() = 0;
virtual saveGameHandle_t EnumerateSaveGamesAsync() = 0;
virtual saveGameHandle_t DeleteSaveGameSync( const char * name ) = 0;
virtual saveGameHandle_t DeleteSaveGameAsync( const char * name ) = 0;
virtual bool IsSaveGameCompletedFromHandle( const saveGameHandle_t & handle ) const = 0;
virtual void CancelSaveGameWithHandle( const saveGameHandle_t & handle ) = 0;
// Needed for main menu integration
virtual bool IsEnumerating() const = 0;
virtual saveGameHandle_t GetEnumerationHandle() const = 0;
// Returns the known list of savegames, must first enumerate for the savegames ( via session->Enumerate() )
virtual const saveGameDetailsList_t & GetEnumeratedSavegames() const = 0;
// These are on session and not idGame so it can persist across game deallocations
virtual void SetCurrentSaveSlot( const char * slotName ) = 0;
virtual const char * GetCurrentSaveSlot() const = 0;
// Error checking
virtual bool IsDLCAvailable( const char * mapName ) = 0;
virtual bool LoadGameCheckDiscNumber( idSaveLoadParms & parms ) = 0;
//=====================================================================================================
// GamerCard UI
//=====================================================================================================
virtual void ShowLobbyUserGamerCardUI( lobbyUserID_t lobbyUserID ) = 0;
//=====================================================================================================
virtual void UpdateRichPresence() = 0;
virtual void SendUsercmds( idBitMsg & msg ) = 0;
virtual void SendSnapshot( class idSnapShot & ss ) = 0;
virtual int GetInputRouting( int inputRouting[ MAX_INPUT_DEVICES ] );
virtual void UpdateSignInManager() = 0;
idSignInManagerBase & GetSignInManager() { return *signInManager; }
idSaveGameManager & GetSaveGameManager() { return *saveGameManager; }
idAchievementSystem & GetAchievementSystem() { return *achievementSystem; }
bool HasSignInManager() const { return ( signInManager != NULL ); }
bool HasAchievementSystem() const { return ( achievementSystem != NULL ); }
virtual bool IsSystemUIShowing() const = 0;
virtual void SetSystemUIShowing( bool show ) = 0;
//=====================================================================================================
// Voice chat
//=====================================================================================================
virtual voiceState_t GetLobbyUserVoiceState( lobbyUserID_t lobbyUserID ) = 0;
virtual voiceStateDisplay_t GetDisplayStateFromVoiceState( voiceState_t voiceState ) const = 0;
virtual void ToggleLobbyUserVoiceMute( lobbyUserID_t lobbyUserID ) = 0;
virtual void SetActiveChatGroup( int groupIndex ) = 0;
virtual void CheckVoicePrivileges() = 0;
virtual void SetVoiceGroupsToTeams() = 0;
virtual void ClearVoiceGroups() = 0;
//=====================================================================================================
// Bandwidth / QoS checking
//=====================================================================================================
virtual bool StartOrContinueBandwidthChallenge( bool forceStart ) = 0;
virtual void DebugSetPeerSnaprate( int peerIndex, int snapRateMS ) = 0;
virtual float GetIncomingByteRate() = 0;
//=====================================================================================================
// Invites
//=====================================================================================================
virtual void HandleBootableInvite( int64 lobbyId = 0 ) = 0;
virtual void HandleExitspawnInvite( const lobbyConnectInfo_t & connectInfo ) {}
virtual void ClearBootableInvite() = 0;
virtual void ClearPendingInvite() = 0;
virtual bool HasPendingBootableInvite() = 0;
virtual void SetDiscSwapMPInvite( void * parm ) = 0;
virtual void * GetDiscSwapMPInviteParms() = 0;
virtual bool IsDiscSwapMPInviteRequested() const = 0;
//=====================================================================================================
// Notifications
//=====================================================================================================
// This is called when a LocalUser is signed in/out
virtual void OnLocalUserSignin( idLocalUser * user ) = 0;
virtual void OnLocalUserSignout( idLocalUser * user ) = 0;
// This is called when the master LocalUser is signed in/out, these are called after OnLocalUserSignin/out()
virtual void OnMasterLocalUserSignout() = 0;
virtual void OnMasterLocalUserSignin() = 0;
// After a local user has signed in and their profile has loaded
virtual void OnLocalUserProfileLoaded( idLocalUser * user ) = 0;
protected:
idSignInManagerBase * signInManager; // pointer so we can treat dynamically bind platform-specific impl
idSaveGameManager * saveGameManager;
idAchievementSystem * achievementSystem; // pointer so we can treat dynamically bind platform-specific impl
idDedicatedServerSearch * dedicatedServerSearch;
};
/*
========================
idSession::idGetInputRouting
========================
*/
ID_INLINE int idSession::GetInputRouting( int inputRouting[ MAX_INPUT_DEVICES ] ) {
for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) {
inputRouting[ i ] = -1;
}
inputRouting[0] = 0;
return 1;
}
extern idSession * session;
#endif // __SYS_SESSION_H__
+409
View File
@@ -0,0 +1,409 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "../framework/Common_local.h"
#include "sys_session_local.h"
// The more the idLobby class needs to call back into this class, the more likely we're doing something wrong, and there is a better way.
/*
========================
idSessionLocalCallbacks::BecomingHost
This is called when
========================
*/
bool idSessionLocalCallbacks::BecomingHost( idLobby & lobby ) {
if ( lobby.lobbyType == idLobby::TYPE_GAME ) {
if ( sessionLocal->GetActivePlatformLobby() != &lobby ) {
idLib::Printf( "BecomingHost: Must be past the party lobby to become host of a game lobby.\n" );
return false;
}
if ( sessionLocal->localState == idSessionLocal::STATE_INGAME || sessionLocal->localState == idSessionLocal::STATE_LOADING ) {
// If we are in a game, go back to the lobby before becoming the new host of a game lobby
sessionLocal->SetState( idSessionLocal::STATE_GAME_LOBBY_PEER );
// session mgr housekeeping that would usually be done through the standard EndMatch path
sessionLocal->EndMatchForMigration();
}
}
return true;
}
/*
========================
idSessionLocalCallbacks::BecameHost
========================
*/
void idSessionLocalCallbacks::BecameHost( idLobby & lobby ) {
// If we were in the lobby when we switched to host, then set the right state
if ( lobby.lobbyType == idLobby::TYPE_PARTY && sessionLocal->localState == idSessionLocal::STATE_PARTY_LOBBY_PEER ) {
sessionLocal->SetState( idSessionLocal::STATE_PARTY_LOBBY_HOST );
} else if ( lobby.lobbyType == idLobby::TYPE_GAME && sessionLocal->localState == idSessionLocal::STATE_GAME_LOBBY_PEER ) {
sessionLocal->SetState( idSessionLocal::STATE_GAME_LOBBY_HOST );
}
}
/*
========================
idSessionLocalCallbacks::BecomingPeer
========================
*/
bool idSessionLocalCallbacks::BecomingPeer( idLobby & lobby ) {
if ( lobby.lobbyType == idLobby::TYPE_GAME ) {
if ( sessionLocal->localState == idSessionLocal::STATE_INGAME || sessionLocal->localState == idSessionLocal::STATE_LOADING ) {
// Go to the party lobby while we try to connect to the new host
// This isn't totally necessary but we want to end the current game now and go to some screen.
// When the connection goes through or fails will send the session mgr to the appropriate state (game lobby or main menu)
// What happens if we got the game migration before the party migration?
sessionLocal->SetState( sessionLocal->GetPartyLobby().IsHost() ? idSessionLocal::STATE_PARTY_LOBBY_HOST : idSessionLocal::STATE_PARTY_LOBBY_PEER );
// session mgr housekeeping that would usually be done through the standard EndMatch path
sessionLocal->EndMatchForMigration();
return true; // return true tells the session that we want him to tell us when the connects/fails
}
}
return false;
}
/*
========================
idSessionLocalCallbacks::BecamePeer
========================
*/
void idSessionLocalCallbacks::BecamePeer( idLobby & lobby ) {
if ( lobby.lobbyType == idLobby::TYPE_GAME ) {
sessionLocal->SetState( idSessionLocal::STATE_GAME_LOBBY_PEER );
}
}
/*
========================
idSessionLocalCallbacks::FailedGameMigration
========================
*/
void idSessionLocalCallbacks::FailedGameMigration( idLobby & lobby ) {
// We failed to complete a game migration this could happen for a couple reasons:
// -The network invites failed / failed to join migrated session
// -There was nobody to invite
lobby.ResetAllMigrationState();
if ( lobby.lobbyType == idLobby::TYPE_GAME ) { // this check is a redundant since we should only get this CB from the game session
sessionLocal->SetState( idSessionLocal::STATE_GAME_LOBBY_HOST );
// Make sure the sessions are joinable again
sessionLocal->EndSessions();
}
}
/*
========================
idSessionLocalCallbacks::MigrationEnded
========================
*/
void idSessionLocalCallbacks::MigrationEnded( idLobby & lobby ) {
if ( lobby.migrationInfo.persistUntilGameEndsData.wasMigratedGame ) {
#if 1
if ( lobby.lobbyType == idLobby::TYPE_GAME || ( lobby.lobbyType == idLobby::TYPE_PARTY && session->GetState() <= idSession::PARTY_LOBBY ) ) {
common->Dialog().ClearDialog( GDM_MIGRATING );
common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
if ( lobby.GetNumLobbyUsers() <= 1 ) {
if ( MatchTypeHasStats( lobby.parms.matchFlags ) ) {
common->Dialog().AddDialog( GDM_MIGRATING_FAILED_DISBANDED_STATS, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // Game has disbanded
} else {
common->Dialog().AddDialog( GDM_MIGRATING_FAILED_DISBANDED, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // Game has disbanded
}
} else {
//common->Dialog().AddDialog( GDM_MIGRATING_FAILED_CONNECTION, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // Lost connection to game
if ( lobby.lobbyType == idLobby::TYPE_GAME && MatchTypeHasStats( lobby.parms.matchFlags ) ) {
// This means we came from a public match, so tell them they didn't lose stats
common->Dialog().AddDialog( GDM_HOST_CONNECTION_LOST_STATS, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // The connection to the host has been lost. This game will not count towards your ranking.
} else {
// This means we came from a private match, just say host quit
common->Dialog().AddDialog( GDM_HOST_CONNECTION_LOST, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // The connection to the host has been lost.
}
}
lobby.ResetAllMigrationState();
// Make sure the sessions are joinable again
sessionLocal->EndSessions();
}
#else
// If we get here, we migrated from a game
if ( lobby.GetNumLobbyUsers() <= 1 && lobby.lobbyType == idLobby::TYPE_GAME ) {
if ( !MatchTypeIsJoinInProgress( lobby.parms.matchFlags ) ) {
// Handles 'soft' failed game migration where we migrated from a game and are now alone
gameDialogMessages_t errorDlg = GDM_INVALID;
lobby.migrationInfo.persistUntilGameEndsData.hasGameData = false; // never restart the game if we are by ourselves
if ( lobby.migrationInfo.invites.Num() > 0 ) {
// outstanding invites: migration failed
errorDlg = ( MatchTypeHasStats( lobby.migrateMsgFlags ) && ( sessionLocal->GetFlushedStats() == false ) ) ? GDM_MIGRATING_FAILED_CONNECTION_STATS : GDM_MIGRATING_FAILED_CONNECTION;
} else {
// there was no one to invite
errorDlg = ( MatchTypeHasStats( lobby.migrateMsgFlags ) && ( sessionLocal->GetFlushedStats() == false ) ) ? GDM_MIGRATING_FAILED_DISBANDED_STATS : GDM_MIGRATING_FAILED_DISBANDED;
}
if ( errorDlg != GDM_INVALID ) {
common->Dialog().AddDialog( errorDlg, DIALOG_ACCEPT, NULL, NULL, false );
}
common->Dialog().ClearDialog( GDM_MIGRATING );
common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
FailedGameMigration( lobby );
}
} else if ( lobby.lobbyType == idLobby::TYPE_PARTY ) {
if ( session->GetState() <= idSession::PARTY_LOBBY ) {
// We got dropped the party lobby, let them know what happened
common->Dialog().ClearDialog( GDM_MIGRATING );
common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
if ( lobby.GetNumLobbyUsers() <= 1 ) {
common->Dialog().AddDialog( GDM_MIGRATING_FAILED_DISBANDED, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // Game has disbanded
} else {
//common->Dialog().AddDialog( GDM_MIGRATING_FAILED_CONNECTION, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // Lost connection to game
common->Dialog().AddDialog( GDM_HOST_CONNECTION_LOST_STATS, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true );
}
lobby.ResetAllMigrationState();
// Make sure the sessions are joinable again
sessionLocal->EndSessions();
}
}
#endif
} else if ( lobby.GetNumLobbyUsers() <= 1 && session->GetState() == idSession::PARTY_LOBBY ) {
// If they didn't come from a game, and are by themselves, just show the lobby disband msg
common->Dialog().AddDialog( GDM_LOBBY_DISBANDED, DIALOG_ACCEPT, NULL, NULL, false, "", 0, true ); // The lobby you were previously in has disbanded
// Make sure the sessions are joinable again
sessionLocal->EndSessions();
}
}
/*
========================
idSessionLocalCallbacks::GoodbyeFromHost
========================
*/
void idSessionLocalCallbacks::GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType ) {
sessionLocal->GoodbyeFromHost( lobby, peerNum, remoteAddress, msgType );
}
/*
========================
idSessionLocalCallbacks::AnyPeerHasAddress
========================
*/
bool idSessionLocalCallbacks::AnyPeerHasAddress( const lobbyAddress_t & remoteAddress ) const {
return sessionLocal->GetPartyLobby().FindAnyPeer( remoteAddress ) || sessionLocal->GetGameLobby().FindAnyPeer( remoteAddress );
}
/*
========================
idSessionLocalCallbacks::RecvLeaderboardStats
========================
*/
void idSessionLocalCallbacks::RecvLeaderboardStats( idBitMsg & msg ) {
// Steam and PS3 just write them as they come per player, they don't need to flush
sessionLocal->RecvLeaderboardStatsForPlayer( msg );
}
/*
========================
idSessionLocalCallbacks::ReceivedFullSnap
========================
*/
void idSessionLocalCallbacks::ReceivedFullSnap() {
// If we received a full snap, then we can transition into the INGAME state
sessionLocal->numFullSnapsReceived++;
if ( sessionLocal->numFullSnapsReceived < 2 ) {
return;
}
if ( sessionLocal->localState != idSessionLocal::STATE_INGAME ) {
sessionLocal->GetActingGameStateLobby().QueueReliableMessage( sessionLocal->GetActingGameStateLobby().host, idLobby::RELIABLE_IN_GAME ); // Let host know we are in game now
sessionLocal->SetState( idSessionLocal::STATE_INGAME );
}
}
/*
========================
idSessionLocalCallbacks::LeaveGameLobby
========================
*/
void idSessionLocalCallbacks::LeaveGameLobby() {
// Make sure we're in the game lobby
if ( session->GetState() != idSession::GAME_LOBBY ) {
return;
}
// If we're the host of the party, only we are allowed to make this call
if ( sessionLocal->GetPartyLobby().IsHost() ) {
return;
}
sessionLocal->GetGameLobby().Shutdown();
sessionLocal->SetState( idSessionLocal::STATE_PARTY_LOBBY_PEER );
}
/*
========================
idSessionLocalCallbacks::PrePickNewHost
This is called when we have determined that we need to pick a new host.
Call PickNewHostInternal to continue on with the host picking process.
========================
*/
void idSessionLocalCallbacks::PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost ) {
sessionLocal->PrePickNewHost( lobby, forceMe, inviteOldHost );
}
/*
========================
idSessionLocalCallbacks::PreMigrateInvite
This is called just before we get invited to a migrated session
If we return false, the invite will be ignored
========================
*/
bool idSessionLocalCallbacks::PreMigrateInvite( idLobby & lobby ) {
return sessionLocal->PreMigrateInvite( lobby );
}
/*
========================
idSessionLocalCallbacks::ConnectAndMoveToLobby
========================
*/
void idSessionLocalCallbacks::ConnectAndMoveToLobby( idLobby::lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForPartyOk ) {
// See if we are already in the game lobby
idLobby * lobby = sessionLocal->GetLobbyFromType( destLobbyType );
if ( lobby == NULL ) {
idLib::Printf( "RELIABLE_CONNECT_AND_MOVE_TO_LOBBY: Invalid lobby type.\n" );
return;
}
if ( lobby->lobbyBackend != NULL && lobby->lobbyBackend->IsOwnerOfConnectInfo( connectInfo ) ) {
idLib::Printf( "RELIABLE_CONNECT_AND_MOVE_TO_LOBBY: Already in lobby.\n" );
return;
}
// See if we are in a game, or loading into a game. If so, ignore invites from our party host
if ( destLobbyType == idLobby::TYPE_GAME || destLobbyType == idLobby::TYPE_GAME_STATE ) {
if ( GetState() == idSession::INGAME || GetState() == idSession::LOADING ) {
idLib::Printf( "RELIABLE_CONNECT_AND_MOVE_TO_LOBBY: In a different game, ignoring.\n" );
return;
}
}
// End current game lobby
lobby->Shutdown();
// waitForPartyOk will be true if the party host wants us to wait for his ok to stay in the lobby
lobby->waitForPartyOk = waitForPartyOk;
// Connect to new game lobby
sessionLocal->ConnectAndMoveToLobby( *lobby, connectInfo, true ); // Consider this an invite if party host told us to connect
}
/*
========================
idSessionLocalCallbacks::HandleServerQueryRequest
========================
*/
void idSessionLocalCallbacks::HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) {
sessionLocal->HandleServerQueryRequest( remoteAddr, msg, msgType );
}
/*
========================
idSessionLocalCallbacks::HandleServerQueryAck
========================
*/
void idSessionLocalCallbacks::HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) {
sessionLocal->HandleServerQueryAck( remoteAddr, msg );
}
extern idCVar net_headlessServer;
/*
========================
idSessionLocalCallbacks::HandlePeerMatchParamUpdate
========================
*/
void idSessionLocalCallbacks::HandlePeerMatchParamUpdate( int peer, int msg ) {
if ( net_headlessServer.GetBool() ) {
sessionLocal->storedPeer = peer;
sessionLocal->storedMsgType = msg;
}
}
/*
========================
idSessionLocalCallbacks::CreateLobbyBackend
========================
*/
idLobbyBackend * idSessionLocalCallbacks::CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) {
return sessionLocal->CreateLobbyBackend( p, skillLevel, lobbyType );
}
/*
========================
idSessionLocalCallbacks::FindLobbyBackend
========================
*/
idLobbyBackend * idSessionLocalCallbacks::FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) {
return sessionLocal->FindLobbyBackend( p, numPartyUsers, skillLevel, lobbyType );
}
/*
========================
idSessionLocalCallbacks::JoinFromConnectInfo
========================
*/
idLobbyBackend * idSessionLocalCallbacks::JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo , idLobbyBackend::lobbyBackendType_t lobbyType ) {
return sessionLocal->JoinFromConnectInfo( connectInfo, lobbyType );
}
/*
========================
idSessionLocalCallbacks::DestroyLobbyBackend
========================
*/
void idSessionLocalCallbacks::DestroyLobbyBackend( idLobbyBackend * lobbyBackend ) {
sessionLocal->DestroyLobbyBackend( lobbyBackend );
}
File diff suppressed because it is too large Load Diff
+700
View File
@@ -0,0 +1,700 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#undef private
#undef protected
#include "win32/win_achievements.h"
#include "win32/win_signin.h"
#include "sys_lobby_backend.h"
#include "sys_lobby.h"
class idSaveGameProcessorNextMap;
class idSaveGameProcessorSaveGame;
class idSaveGameProcessorLoadGame;
class idSaveGameProcessorDelete;
class idSaveGameProcessorEnumerateGames;
/*
================================================
idLobbyStub
================================================
*/
class idLobbyStub : public idLobbyBase {
public:
virtual bool IsHost() const { return false; }
virtual bool IsPeer() const { return false; }
virtual bool HasActivePeers() const { return false; }
virtual int GetNumLobbyUsers() const { return 0; }
virtual int GetNumActiveLobbyUsers() const { return 0; }
virtual bool IsLobbyUserConnected( int index ) const { return false; }
virtual lobbyUserID_t GetLobbyUserIdByOrdinal( int userIndex ) const { return lobbyUserID_t(); }
virtual int GetLobbyUserIndexFromLobbyUserID( lobbyUserID_t lobbyUserID ) const { return -1; }
virtual void SendReliable( int type, idBitMsg & msg, bool callReceiveReliable = true, peerMask_t sessionUserMask = MAX_UNSIGNED_TYPE( peerMask_t ) ) {}
virtual void SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg & msg ) {}
virtual void SendReliableToHost( int type, idBitMsg & msg ) {}
virtual const char * GetLobbyUserName( lobbyUserID_t lobbyUserID ) const { return "INVALID"; }
virtual void KickLobbyUser( lobbyUserID_t lobbyUserID ) {}
virtual bool IsLobbyUserValid( lobbyUserID_t lobbyUserID ) const { return false; }
virtual bool IsLobbyUserLoaded( lobbyUserID_t lobbyUserID ) const { return false; }
virtual bool LobbyUserHasFirstFullSnap( lobbyUserID_t lobbyUserID ) const { return false; }
virtual void EnableSnapshotsForLobbyUser( lobbyUserID_t lobbyUserID ) {}
virtual int GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const { return 0; }
virtual bool GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const { return false; }
virtual bool GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const { return false; }
virtual int GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const { return 0; }
virtual int GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const { return 0; }
virtual int GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const { return 0; }
virtual bool SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber ) { return false; }
virtual int GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const { return 0; }
virtual idPlayerProfile * GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID ) { return NULL; }
virtual idLocalUser * GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID ) { return NULL; }
virtual int GetNumLobbyUsersOnTeam( int teamNumber ) const { return 0; }
virtual int PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const { return -1; }
virtual int GetPeerTimeSinceLastPacket( int peerIndex ) const { return 0; }
virtual int PeerIndexForHost() const { return -1; }
virtual lobbyUserID_t AllocLobbyUserSlotForBot( const char * botName ) { return lobbyUserID_t(); }
virtual void RemoveBotFromLobbyUserList( lobbyUserID_t lobbyUserID ) {}
virtual bool GetLobbyUserIsBot( lobbyUserID_t lobbyUserID ) const { return false; }
virtual const char * GetHostUserName() const { return "INVALID"; }
virtual const idMatchParameters & GetMatchParms() const { return fakeParms; }
virtual bool IsLobbyFull() const { return false; }
virtual bool EnsureAllPeersHaveBaseState() { return false; }
virtual bool AllPeersInGame() const { return false; }
virtual int GetNumConnectedPeers() const { return 0; }
virtual int GetNumConnectedPeersInGame() const { return 0; }
virtual int PeerIndexOnHost() const { return -1; }
virtual bool IsPeerDisconnected( int peerIndex ) const { return false; }
virtual bool AllPeersHaveStaleSnapObj( int objId ) { return false; }
virtual bool AllPeersHaveExpectedSnapObj( int objId ) { return false; }
virtual void RefreshSnapObj( int objId ) {}
virtual void MarkSnapObjDeleted( int objId ) {}
virtual void AddSnapObjTemplate( int objID, idBitMsg & msg ) {}
virtual void DrawDebugNetworkHUD() const {}
virtual void DrawDebugNetworkHUD2() const {}
virtual void DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw ) {}
private:
idMatchParameters fakeParms;
};
/*
================================================
idSessionLocal
================================================
*/
class idSessionLocal : public idSession {
friend class idLeaderboards;
friend class idStatsSession;
friend class idLobbyBackend360;
friend class idLobbyBackendPS3;
friend class idSessionLocalCallbacks;
friend class idPsnAsyncSubmissionLookupPS3_TitleStorage;
friend class idNetSessionPort;
friend class lobbyAddress_t;
protected:
//=====================================================================================================
// Mixed Common/Platform enums/structs
//=====================================================================================================
// Overall state of the session
enum state_t {
STATE_PRESS_START, // We are at press start
STATE_IDLE, // We are at the main menu
STATE_PARTY_LOBBY_HOST, // We are in the party lobby menu as host
STATE_PARTY_LOBBY_PEER, // We are in the party lobby menu as a peer
STATE_GAME_LOBBY_HOST, // We are in the game lobby as a host
STATE_GAME_LOBBY_PEER, // We are in the game lobby as a peer
STATE_GAME_STATE_LOBBY_HOST, // We are in the game state lobby as a host
STATE_GAME_STATE_LOBBY_PEER, // We are in the game state lobby as a peer
STATE_CREATE_AND_MOVE_TO_PARTY_LOBBY, // We are creating a party lobby, and will move to that state when done
STATE_CREATE_AND_MOVE_TO_GAME_LOBBY, // We are creating a game lobby, and will move to that state when done
STATE_CREATE_AND_MOVE_TO_GAME_STATE_LOBBY, // We are creating a game state lobby, and will move to that state when done
STATE_FIND_OR_CREATE_MATCH,
STATE_CONNECT_AND_MOVE_TO_PARTY,
STATE_CONNECT_AND_MOVE_TO_GAME,
STATE_CONNECT_AND_MOVE_TO_GAME_STATE,
STATE_BUSY, // Doing something internally like a QoS/bandwidth challenge
// These are last, so >= STATE_LOADING tests work
STATE_LOADING, // We are loading the map, preparing to go into a match
STATE_INGAME, // We are currently in a match
NUM_STATES
};
enum connectType_t {
CONNECT_NONE = 0,
CONNECT_DIRECT = 1,
CONNECT_FIND_OR_CREATE = 2,
};
enum pendingInviteMode_t {
PENDING_INVITE_NONE = 0, // No invite waiting
PENDING_INVITE_WAITING = 1, // Invite is waiting
PENDING_SELF_INVITE_WAITING = 2, // We invited ourselves to a match
};
struct contentData_t {
bool isMounted;
idStrStatic<128> displayName;
idStrStatic< MAX_OSPATH > packageFileName;
idStrStatic< MAX_OSPATH > rootPath;
int dlcID;
};
public:
idSessionLocal();
virtual ~idSessionLocal();
void InitBaseState();
virtual bool IsPlatformPartyInLobby();
// Downloadable Content
virtual int GetNumContentPackages() const;
virtual int GetContentPackageID( int contentIndex ) const;
virtual const char * GetContentPackagePath( int contentIndex ) const;
virtual int GetContentPackageIndexForID( int contentID ) const;
virtual bool GetSystemMarketplaceHasNewContent() const { return marketplaceHasNewContent; }
virtual void SetSystemMarketplaceHasNewContent( bool hasNewContent ) { marketplaceHasNewContent = hasNewContent; }
// Lobby management
virtual void CreatePartyLobby( const idMatchParameters & parms_ );
virtual void FindOrCreateMatch( const idMatchParameters & parms );
virtual void CreateMatch( const idMatchParameters & parms_ );
virtual void CreateGameStateLobby( const idMatchParameters & parms_ );
virtual void UpdatePartyParms( const idMatchParameters & parms_ );
virtual void UpdateMatchParms( const idMatchParameters & parms_ );
virtual void StartMatch();
virtual void SetSessionOption( sessionOption_t option ) { sessionOptions |= option; }
virtual void ClearSessionOption( sessionOption_t option ) { sessionOptions &= ~option; }
virtual sessionState_t GetBackState();
virtual void Cancel();
virtual void MoveToPressStart();
virtual void FinishDisconnect();
virtual bool ShouldShowMigratingDialog() const; // Note this is not in sys_session.h
virtual bool IsCurrentLobbyMigrating() const;
virtual bool IsLosingConnectionToHost() const;
// Migration
virtual bool WasMigrationGame() const;
virtual bool ShouldRelaunchMigrationGame() const;
virtual bool GetMigrationGameData( idBitMsg & msg, bool reading );
virtual bool GetMigrationGameDataUser( lobbyUserID_t lobbyUserID, idBitMsg & msg, bool reading );
virtual bool WasGameLobbyCoalesced() const { return gameLobbyWasCoalesced; }
// Misc
virtual int GetLoadingID() { return loadingID; }
virtual bool IsAboutToLoad() const { return GetGameLobby().IsLobbyActive() && GetGameLobby().startLoadingFromHost; }
virtual bool GetMatchParamUpdate( int &peer, int &msg );
virtual int GetInputRouting( int inputRouting[ MAX_INPUT_DEVICES ] );
virtual void EndMatch( bool premature=false ); // Meant for host to end match gracefully, go back to lobby, tally scores, etc
virtual void MatchFinished(); // this is for when the game is over before we go back to lobby. Need this incase the host leaves during this time
virtual void QuitMatch(); // Meant for host or peer to quit the match before it ends, will instigate host migration, etc
virtual void QuitMatchToTitle(); // Will forcefully quit the match and return to the title screen.
virtual void LoadingFinished();
virtual void Pump();
virtual void ProcessSnapAckQueue();
virtual sessionState_t GetState() const;
virtual const char * GetStateString() const ;
virtual void SendUsercmds( idBitMsg & msg );
virtual void SendSnapshot( idSnapShot & ss );
virtual const char * GetPeerName( int peerNum );
virtual const char * GetLocalUserName( int i ) const { return signInManager->GetLocalUserByIndex( i )->GetGamerTag(); }
virtual void UpdateSignInManager();
virtual idPlayerProfile * GetProfileFromMasterLocalUser();
virtual void PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost );
virtual bool PreMigrateInvite( idLobby & lobby );
//=====================================================================================================
// Title Storage Vars
//=====================================================================================================
virtual float GetTitleStorageFloat( const char * name, float defaultFloat ) const { return titleStorageVars.GetFloat( name, defaultFloat ); }
virtual int GetTitleStorageInt( const char * name, int defaultInt ) const { return titleStorageVars.GetInt( name, defaultInt ); }
virtual bool GetTitleStorageBool( const char * name, bool defaultBool ) const { return titleStorageVars.GetBool( name, defaultBool ); }
virtual const char * GetTitleStorageString( const char * name, const char * defaultString ) const { return titleStorageVars.GetString( name, defaultString ); }
virtual bool GetTitleStorageFloat( const char * name, float defaultFloat, float & out ) const { return titleStorageVars.GetFloat( name, defaultFloat, out ); }
virtual bool GetTitleStorageInt( const char * name, int defaultInt, int & out ) const { return titleStorageVars.GetInt( name, defaultInt, out ); }
virtual bool GetTitleStorageBool( const char * name, bool defaultBool, bool & out ) const { return titleStorageVars.GetBool( name, defaultBool, out ); }
virtual bool GetTitleStorageString( const char * name, const char * defaultString, const char ** out ) const { return titleStorageVars.GetString( name, defaultString, out ); }
virtual bool IsTitleStorageLoaded() { return titleStorageLoaded; }
//=====================================================================================================
// Voice chat
//=====================================================================================================
virtual voiceState_t GetLobbyUserVoiceState( lobbyUserID_t lobbyUserID );
virtual voiceStateDisplay_t GetDisplayStateFromVoiceState( voiceState_t voiceState ) const;
virtual void ToggleLobbyUserVoiceMute( lobbyUserID_t lobbyUserID );
virtual void SetActiveChatGroup( int groupIndex );
virtual void UpdateMasterUserHeadsetState();
//=====================================================================================================
// Bandwidth / QoS checking
//=====================================================================================================
virtual bool StartOrContinueBandwidthChallenge( bool forceStart );
virtual void DebugSetPeerSnaprate( int peerIndex, int snapRateMS );
virtual float GetIncomingByteRate();
//=====================================================================================================
// Invites
//=====================================================================================================
virtual void HandleBootableInvite( int64 lobbyId = 0 ) = 0;
virtual void ClearBootableInvite() = 0;
virtual void ClearPendingInvite() = 0;
virtual bool HasPendingBootableInvite() = 0;
virtual void SetDiscSwapMPInvite( void * parm ) = 0; // call to request a discSwap multiplayer invite
virtual void * GetDiscSwapMPInviteParms() = 0;
virtual bool IsDiscSwapMPInviteRequested() const { return inviteInfoRequested; }
bool GetFlushedStats() { return flushedStats; }
void SetFlushedStats( bool _flushedStats ) { flushedStats = _flushedStats; }
//=====================================================================================================
// Notifications
//=====================================================================================================
// This is called when a LocalUser is signed in/out
virtual void OnLocalUserSignin( idLocalUser * user );
virtual void OnLocalUserSignout( idLocalUser * user );
// This is called when the master LocalUser is signed in/out, these are called after OnLocalUserSignin/out()
virtual void OnMasterLocalUserSignout();
virtual void OnMasterLocalUserSignin();
// After a local user has signed in and their profile has loaded
virtual void OnLocalUserProfileLoaded( idLocalUser * user );
//=====================================================================================================
// Platform specific (different platforms implement these differently)
//=====================================================================================================
virtual void Initialize() = 0;
virtual void Shutdown() = 0;
virtual void InitializeSoundRelatedSystems() = 0;
virtual void ShutdownSoundRelatedSystems() = 0;
virtual void PlatformPump() = 0;
virtual void InviteFriends() = 0;
virtual void InviteParty() = 0;
virtual void ShowPartySessions() = 0;
virtual bool ProcessInputEvent( const sysEvent_t * ev ) = 0;
// Play with Friends server listing
virtual int NumServers() const = 0;
virtual void ListServers( const idCallback & callback ) = 0;
virtual void ListServersCommon();
virtual void CancelListServers() = 0;
virtual void ConnectToServer( int i ) = 0;
virtual const serverInfo_t * ServerInfo( int i ) const = 0;
virtual const idList< idStr > * ServerPlayerList( int i );
virtual void ShowServerGamerCardUI( int i ) = 0;
virtual void HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) = 0;
virtual void HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) = 0;
// System UI
virtual bool IsSystemUIShowing() const = 0;
virtual void SetSystemUIShowing( bool show ) = 0;
virtual void ShowSystemMarketplaceUI() const = 0;
virtual void ShowLobbyUserGamerCardUI( lobbyUserID_t lobbyUserID ) = 0;
// Leaderboards
virtual void LeaderboardUpload( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats, const idFile_Memory * attachment = NULL ) = 0;
virtual void LeaderboardDownload( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int startingRank, int numRows, const idLeaderboardCallback & callback ) = 0;
virtual void LeaderboardDownloadAttachment( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int64 attachmentID ) = 0;
// Scoring (currently just for TrueSkill)
virtual void SetLobbyUserRelativeScore( lobbyUserID_t lobbyUserID, int relativeScore, int team ) = 0;
virtual void LeaderboardFlush() = 0;
//=====================================================================================================i'
// Savegames
//=====================================================================================================
virtual saveGameHandle_t SaveGameSync( const char * name, const saveFileEntryList_t & files, const idSaveGameDetails & description );
virtual saveGameHandle_t SaveGameAsync( const char * name, const saveFileEntryList_t & files, const idSaveGameDetails & description );
virtual saveGameHandle_t LoadGameSync( const char * name, saveFileEntryList_t & files );
virtual saveGameHandle_t EnumerateSaveGamesSync();
virtual saveGameHandle_t EnumerateSaveGamesAsync();
virtual saveGameHandle_t DeleteSaveGameSync( const char * name );
virtual saveGameHandle_t DeleteSaveGameAsync( const char * name );
virtual bool IsSaveGameCompletedFromHandle( const saveGameHandle_t & handle ) const { return saveGameManager->IsSaveGameCompletedFromHandle( handle ); }
virtual void CancelSaveGameWithHandle( const saveGameHandle_t & handle ) { GetSaveGameManager().CancelWithHandle( handle ); }
virtual const saveGameDetailsList_t & GetEnumeratedSavegames() const { return saveGameManager->GetEnumeratedSavegames(); }
virtual bool IsEnumerating() const;
virtual saveGameHandle_t GetEnumerationHandle() const;
virtual void SetCurrentSaveSlot( const char * slotName ) { currentSaveSlot = slotName; }
virtual const char * GetCurrentSaveSlot() const { return currentSaveSlot.c_str(); }
// Notifications
void OnSaveCompleted( idSaveLoadParms * parms );
void OnLoadCompleted( idSaveLoadParms * parms );
void OnDeleteCompleted( idSaveLoadParms * parms );
void OnEnumerationCompleted( idSaveLoadParms * parms );
// Error checking
virtual bool IsDLCAvailable( const char * mapName );
virtual bool LoadGameCheckDiscNumber( idSaveLoadParms & parms );
bool LoadGameCheckDescriptionFile( idSaveLoadParms & parms );
// Downloadable Content
virtual void EnumerateDownloadableContent() = 0;
void DropClient( int peerNum, int session );
protected:
float GetUpstreamDropRate() { return upstreamDropRate; }
float GetUpstreamQueueRate() { return upstreamQueueRate; }
int GetQueuedBytes() { return queuedBytes; }
//=====================================================================================================
// Common functions (sys_session_local.cpp)
//=====================================================================================================
void HandleLobbyControllerState( int lobbyType );
virtual void UpdatePendingInvite();
bool HandleState();
// The party and game lobby are the two platform lobbies that notify the backends (Steam/PSN/LIVE of changes)
idLobby & GetPartyLobby() { return partyLobby; }
const idLobby & GetPartyLobby() const { return partyLobby; }
idLobby & GetGameLobby() { return gameLobby; }
const idLobby & GetGameLobby() const { return gameLobby; }
// Game state lobby is the lobby used while in-game. It is so the dedicated server can host this lobby
// and have all platform clients join. It does NOT notify the backends of changes, it's purely for the dedicated
// server to be able to host the in-game lobby.
// Generally, you would call GetActingGameStateLobby. If we are not using game state lobby, GetActingGameStateLobby will return GetGameLobby insread.
idLobby & GetGameStateLobby() { return gameStateLobby; }
const idLobby & GetGameStateLobby() const { return gameStateLobby; }
idLobby & GetActingGameStateLobby();
const idLobby & GetActingGameStateLobby() const;
// GetActivePlatformLobby will return either the game or party lobby, it won't return the game state lobby
// This function is generally used for menus, in-game code should refer to GetActingGameStateLobby
idLobby * GetActivePlatformLobby();
const idLobby * GetActivePlatformLobby() const;
idLobby * GetLobbyFromType( idLobby::lobbyType_t lobbyType );
virtual idLobbyBase & GetPartyLobbyBase() { return partyLobby; }
virtual idLobbyBase & GetGameLobbyBase() { return gameLobby; }
virtual idLobbyBase & GetActingGameStateLobbyBase() { return GetActingGameStateLobby(); }
virtual idLobbyBase & GetActivePlatformLobbyBase();
virtual idLobbyBase & GetLobbyFromLobbyUserID( lobbyUserID_t lobbyUserID );
void SetState( state_t newState );
bool HandlePackets();
void HandleVoiceRestrictionDialog();
void SetDroppedByHost( bool dropped ) { droppedByHost = dropped; }
bool GetDroppedByHost() { return droppedByHost; }
public:
int storedPeer;
int storedMsgType;
protected:
static const char * stateToString[ NUM_STATES ];
state_t localState;
uint32 sessionOptions;
connectType_t connectType;
int connectTime;
idLobby partyLobby;
idLobby gameLobby;
idLobby gameStateLobby;
idLobbyStub stubLobby; // We use this when we request the active lobby when we are not in a lobby (i.e at press start)
int currentID; // The host used this to send out a unique id to all users so we can identify them
class idVoiceChatMgr * voiceChat;
int lastVoiceSendtime;
bool hasShownVoiceRestrictionDialog;
pendingInviteMode_t pendingInviteMode;
int pendingInviteDevice;
lobbyConnectInfo_t pendingInviteConnectInfo;
bool isSysUIShowing;
idDict titleStorageVars;
bool titleStorageLoaded;
int showMigratingInfoStartTime;
int nextGameCoalesceTime;
bool gameLobbyWasCoalesced;
int numFullSnapsReceived;
bool flushedStats;
int loadingID;
bool inviteInfoRequested;
idSaveGameProcessorSaveFiles * processorSaveFiles;
idSaveGameProcessorLoadFiles * processorLoadFiles;
idSaveGameProcessorDelete * processorDelete;
idSaveGameProcessorEnumerateGames * processorEnumerate;
idStr currentSaveSlot;
saveGameHandle_t enumerationHandle;
//------------------------
// State functions
//------------------------
bool State_Party_Lobby_Host();
bool State_Party_Lobby_Peer();
bool State_Game_Lobby_Host();
bool State_Game_Lobby_Peer();
bool State_Game_State_Lobby_Host();
bool State_Game_State_Lobby_Peer();
bool State_Loading();
bool State_InGame();
bool State_Find_Or_Create_Match();
bool State_Create_And_Move_To_Party_Lobby();
bool State_Create_And_Move_To_Game_Lobby();
bool State_Create_And_Move_To_Game_State_Lobby();
bool State_Connect_And_Move_To_Party();
bool State_Connect_And_Move_To_Game();
bool State_Connect_And_Move_To_Game_State();
bool State_Finalize_Connect();
bool State_Busy();
// -----------------------
// Downloadable Content
// -----------------------
static const int MAX_CONTENT_PACKAGES = 16;
idStaticList<contentData_t, MAX_CONTENT_PACKAGES> downloadedContent;
bool marketplaceHasNewContent;
class idQueuePacket {
public:
byte data[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ];
lobbyAddress_t address;
int size;
int time;
bool dedicated;
idQueueNode<idQueuePacket> queueNode;
};
idBlockAlloc< idQueuePacket, 64, TAG_NETWORKING > packetAllocator;
idQueue< idQueuePacket,&idQueuePacket::queueNode > sendQueue;
idQueue< idQueuePacket,&idQueuePacket::queueNode > recvQueue;
float upstreamDropRate; // instant rate in B/s at which we are dropping packets due to simulated upstream saturation
int upstreamDropRateTime;
float upstreamQueueRate; // instant rate in B/s at which queued packets are coming out after local buffering due to upstream saturation
int upstreamQueueRateTime;
int queuedBytes;
int waitingOnGameStateMembersToLeaveTime;
int waitingOnGameStateMembersToJoinTime;
void TickSendQueue();
void QueuePacket( idQueue< idQueuePacket,&idQueuePacket::queueNode > & queue, int time, const lobbyAddress_t & to, const void * data, int size, bool dedicated );
bool ReadRawPacketFromQueue( int time, lobbyAddress_t & from, void * data, int & size, bool & outDedicated, int maxSize );
void SendRawPacket( const lobbyAddress_t & to, const void * data, int size, bool dedicated );
bool ReadRawPacket( lobbyAddress_t & from, void * data, int & size, bool & outDedicated, int maxSize );
void ConnectAndMoveToLobby( idLobby & lobby, const lobbyConnectInfo_t & connectInfo, bool fromInvite );
void GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType );
void WriteLeaderboardToMsg( idBitMsg & msg, const leaderboardDefinition_t * leaderboard, const column_t * stats );
void SendLeaderboardStatsToPlayer( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats );
void RecvLeaderboardStatsForPlayer( idBitMsg & msg );
const leaderboardDefinition_t * ReadLeaderboardFromMsg( idBitMsg & msg, column_t * stats );
bool RequirePersistentMaster();
virtual idNetSessionPort & GetPort( bool dedicated = false ) = 0;
virtual idLobbyBackend * CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) = 0;
virtual idLobbyBackend * FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) = 0;
virtual idLobbyBackend * JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo , idLobbyBackend::lobbyBackendType_t lobbyType ) = 0;
virtual void DestroyLobbyBackend( idLobbyBackend * lobby ) = 0;
virtual void PumpLobbies() = 0;
virtual bool GetLobbyAddressFromNetAddress( const netadr_t & netAddr, lobbyAddress_t & outAddr ) const = 0;
virtual bool GetNetAddressFromLobbyAddress( const lobbyAddress_t & lobbyAddress, netadr_t & outNetAddr ) const = 0;
void HandleDedicatedServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType );
void HandleDedicatedServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg );
void ClearMigrationState();
// this is called when the mathc is over and returning to lobby
void EndMatchInternal( bool premature=false );
// this is called when the game finished and we are in the end match recap
void MatchFinishedInternal();
void EndMatchForMigration();
void MoveToPressStart( gameDialogMessages_t msg );
// Voice chat
void SendVoiceAudio();
void HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg );
void SetVoiceGroupsToTeams();
void ClearVoiceGroups();
// All the new functions going here for now until it can all be cleaned up
void StartSessions();
void EndSessions();
void SetLobbiesAreJoinable( bool joinable );
void MoveToMainMenu(); // End all session (async), and return to IDLE state
bool WaitOnLobbyCreate( idLobby & lobby );
bool DetectDisconnectFromService( bool cancelAndShowMsg );
void HandleConnectionFailed( idLobby & lobby, bool wasFull );
void ConnectToNextSearchResultFailed( idLobby & lobby );
bool HandleConnectAndMoveToLobby( idLobby & lobby );
void VerifySnapshotInitialState();
void ComputeNextGameCoalesceTime();
void StartLoading();
bool ShouldHavePartyLobby();
void ValidateLobbies();
void ValidateLobby( idLobby & lobby );
void ReadTitleStorage( void * buffer, int bufferLen );
bool ReadDLCInfo( idDict & dlcInfo, void * buffer, int bufferLen );
idSessionCallbacks * sessionCallbacks;
int offlineTransitionTimerStart;
bool droppedByHost;
};
/*
========================
idSessionLocalCallbacks
The more the idLobby class needs to call back into this class, the more likely we're doing something wrong, and there is a better way.
========================
*/
class idSessionLocalCallbacks : public idSessionCallbacks {
public:
idSessionLocalCallbacks( idSessionLocal * sessionLocal_ ) { sessionLocal = sessionLocal_; }
virtual idLobby & GetPartyLobby() { return sessionLocal->GetPartyLobby(); }
virtual idLobby & GetGameLobby() { return sessionLocal->GetGameLobby(); }
virtual idLobby & GetActingGameStateLobby() { return sessionLocal->GetActingGameStateLobby(); }
virtual idLobby * GetLobbyFromType( idLobby::lobbyType_t lobbyType ) { return sessionLocal->GetLobbyFromType( lobbyType ); }
virtual int GetUniquePlayerId() const { return sessionLocal->currentID++; }
virtual idSignInManagerBase & GetSignInManager() { return *sessionLocal->signInManager; }
virtual void SendRawPacket( const lobbyAddress_t & to, const void * data, int size, bool useDirectPort ) { sessionLocal->SendRawPacket( to, data, size, useDirectPort ); }
virtual bool BecomingHost( idLobby & lobby );
virtual void BecameHost( idLobby & lobby );
virtual bool BecomingPeer( idLobby & lobby );
virtual void BecamePeer( idLobby & lobby );
virtual void FailedGameMigration( idLobby & lobby );
virtual void MigrationEnded( idLobby & lobby );
virtual void GoodbyeFromHost( idLobby & lobby, int peerNum, const lobbyAddress_t & remoteAddress, int msgType );
virtual uint32 GetSessionOptions() { return sessionLocal->sessionOptions; }
virtual bool AnyPeerHasAddress( const lobbyAddress_t & remoteAddress ) const;
virtual idSession::sessionState_t GetState() const { return sessionLocal->GetState(); }
virtual void ClearMigrationState() { GetPartyLobby().ResetAllMigrationState(); GetGameLobby().ResetAllMigrationState(); }
virtual void EndMatchInternal( bool premature=false ) { sessionLocal->EndMatchInternal( premature ); }
virtual void RecvLeaderboardStats( idBitMsg & msg );
virtual void ReceivedFullSnap();
virtual void LeaveGameLobby();
virtual void PrePickNewHost( idLobby & lobby, bool forceMe, bool inviteOldHost );
virtual bool PreMigrateInvite( idLobby & lobby );
virtual void HandleOobVoiceAudio( const lobbyAddress_t & from, const idBitMsg & msg ) { sessionLocal->HandleOobVoiceAudio( from, msg ); }
virtual void ConnectAndMoveToLobby( idLobby::lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForPartyOk );
virtual idVoiceChatMgr * GetVoiceChat() { return sessionLocal->voiceChat; }
virtual void HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType );
virtual void HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg );
virtual void HandlePeerMatchParamUpdate( int peer, int msg );
virtual idLobbyBackend * CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType );
virtual idLobbyBackend * FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType );
virtual idLobbyBackend * JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo , idLobbyBackend::lobbyBackendType_t lobbyType );
virtual void DestroyLobbyBackend( idLobbyBackend * lobby );
idSessionLocal * sessionLocal;
};
+864
View File
@@ -0,0 +1,864 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "sys_savegame.h"
#include "sys_session_local.h"
#include "sys_session_savegames.h"
extern idCVar saveGame_verbose;
idCVar savegame_error( "savegame_error", "0", CVAR_INTEGER, "Combination of bits that will simulate and error, see 'savegamePrintErrors'. 0 = no error" );
void OutputDetailList( const saveGameDetailsList_t & savegameList );
#pragma region PROCESSORS
/*
================================================================================================
idSaveGameProcessorLoadFiles
================================================================================================
*/
/*
========================
idSaveGameProcessorLoadFiles::InitLoadFiles
========================
*/
bool idSaveGameProcessorLoadFiles::InitLoadFiles( const char * folder_, const saveFileEntryList_t & files, idSaveGameManager::packageType_t type ) {
if ( !idSaveGameProcessor::Init() ) {
return false;
}
parms.directory = AddSaveFolderPrefix( folder_, type );
parms.description.slotName = folder_;
parms.mode = SAVEGAME_MBF_LOAD;
for ( int i = 0; i < files.Num(); ++i ) {
parms.files.Append( files[i] );
}
return true;
}
/*
========================
idSaveGameProcessorLoadFiles::Process
========================
*/
bool idSaveGameProcessorLoadFiles::Process() {
// Platform-specific impl
// This will populate an idFile_Memory with the contents of the save game
// This will not initialize the game, only load the file from the file-system
Sys_ExecuteSavegameCommandAsync( &parms );
return false;
}
/*
================================================================================================
idSaveGameProcessorDelete
================================================================================================
*/
/*
========================
idSaveGameProcessorDelete::Init
========================
*/
bool idSaveGameProcessorDelete::InitDelete( const char * folder_, idSaveGameManager::packageType_t type ) {
if ( !idSaveGameProcessor::Init() ) {
return false;
}
parms.description.slotName = folder_;
parms.directory = AddSaveFolderPrefix( folder_, type );
parms.mode = SAVEGAME_MBF_DELETE_FOLDER;
return true;
}
/*
========================
idSaveGameProcessorDelete::Process
========================
*/
bool idSaveGameProcessorDelete::Process() {
// Platform-specific impl
// This will populate an idFile_Memory with the contents of the save game
// This will not initialize the game, only load the file from the file-system
Sys_ExecuteSavegameCommandAsync( &parms );
return false;
}
/*
================================================================================================
idSaveGameProcessorSaveFiles
================================================================================================
*/
/*
========================
idSaveGameProcessorSaveFiles::InitSave
========================
*/
bool idSaveGameProcessorSaveFiles::InitSave( const char * folder, const saveFileEntryList_t & files, const idSaveGameDetails & descriptionForPS3, idSaveGameManager::packageType_t type ) {
if ( !idSaveGameProcessor::Init() ) {
return false;
}
if ( files.Num() == 0 ) {
idLib::Warning( "No files to save." );
return false;
}
// Setup save system
parms.directory = AddSaveFolderPrefix( folder, type );
parms.mode = SAVEGAME_MBF_SAVE; // do NOT delete the existing files
for ( int i = 0; i < files.Num(); ++i ) {
parms.files.Append( files[i] );
}
this->parms.description = descriptionForPS3;
parms.description.slotName = folder;
return true;
}
/*
========================
idSaveGameProcessorSaveFiles::Process
========================
*/
bool idSaveGameProcessorSaveFiles::Process() {
// Platform-specific implementation
// This will start a worker thread for async operation.
// It will always signal when it's completed.
Sys_ExecuteSavegameCommandAsync( &parms );
return false;
}
/*
================================================================================================
idSaveGameProcessorEnumerateGames
================================================================================================
*/
/*
========================
idSaveGameProcessorEnumerateGames::Process
========================
*/
bool idSaveGameProcessorEnumerateGames::Process() {
parms.mode = SAVEGAME_MBF_ENUMERATE | SAVEGAME_MBF_READ_DETAILS;
// Platform-specific implementation
// This will start a worker thread for async operation.
// It will always signal when it's completed.
Sys_ExecuteSavegameCommandAsync( &parms );
return false;
}
#pragma endregion
/*
========================
idSessionLocal::SaveGameSync
========================
*/
saveGameHandle_t idSessionLocal::SaveGameSync( const char * name, const saveFileEntryList_t & files, const idSaveGameDetails & description ) {
saveGameHandle_t handle = 0;
// serialize the description file behind their back...
saveFileEntryList_t filesWithDetails( files );
idFile_SaveGame * gameDetailsFile = new (TAG_SAVEGAMES) idFile_SaveGame( SAVEGAME_DETAILS_FILENAME, SAVEGAMEFILE_TEXT | SAVEGAMEFILE_AUTO_DELETE );
gameDetailsFile->MakeWritable();
description.descriptors.WriteToIniFile( gameDetailsFile );
filesWithDetails.Append( gameDetailsFile );
if ( processorSaveFiles->InitSave( name, filesWithDetails, description ) ) {
processorSaveFiles->AddCompletedCallback( MakeCallback( this, &idSessionLocal::OnSaveCompleted, &processorSaveFiles->GetParmsNonConst() ) );
handle = GetSaveGameManager().ExecuteProcessorAndWait( processorSaveFiles );
}
// Errors within the process of saving are handled in OnSaveCompleted()
// so that asynchronous save errors are handled the same was as synchronous.
if ( handle == 0 ) {
idSaveLoadParms & parms = processorSaveFiles->GetParmsNonConst();
parms.errorCode = SAVEGAME_E_UNKNOWN;
// Uniform error handling
OnSaveCompleted( &parms );
}
return handle;
}
/*
========================
idSessionLocal::SaveGameAsync
========================
*/
saveGameHandle_t idSessionLocal::SaveGameAsync( const char * name, const saveFileEntryList_t & files, const idSaveGameDetails & description ) {
saveGameHandle_t handle = 0;
// Done this way so we know it will be shutdown properly on early exit or exception
struct local_t {
local_t( idSaveLoadParms * localparms ) : parms( localparms ) {
// Prepare background renderer
}
~local_t() {
// Shutdown background renderer
}
idSaveLoadParms * parms;
} local( &processorSaveFiles->GetParmsNonConst() );
// serialize the description file behind their back...
saveFileEntryList_t filesWithDetails( files );
idFile_SaveGame * gameDetailsFile = new (TAG_SAVEGAMES) idFile_SaveGame( SAVEGAME_DETAILS_FILENAME, SAVEGAMEFILE_TEXT | SAVEGAMEFILE_AUTO_DELETE );
gameDetailsFile->MakeWritable();
description.descriptors.WriteToIniFile( gameDetailsFile );
filesWithDetails.Append( gameDetailsFile );
if ( processorSaveFiles->InitSave( name, filesWithDetails, description ) ) {
processorSaveFiles->AddCompletedCallback( MakeCallback( this, &idSessionLocal::OnSaveCompleted, &processorSaveFiles->GetParmsNonConst() ) );
handle = GetSaveGameManager().ExecuteProcessor( processorSaveFiles );
}
// Errors within the process of saving are handled in OnSaveCompleted()
// so that asynchronous save errors are handled the same was as synchronous.
if ( handle == 0 ) {
idSaveLoadParms & parms = processorSaveFiles->GetParmsNonConst();
parms.errorCode = SAVEGAME_E_UNKNOWN;
common->Dialog().ShowSaveIndicator( false );
// Uniform error handling
OnSaveCompleted( &parms );
}
return handle;
}
/*
========================
idSessionLocal::OnSaveCompleted
========================
*/
void idSessionLocal::OnSaveCompleted( idSaveLoadParms * parms ) {
idLocalUser * master = session->GetSignInManager().GetMasterLocalUser();
if ( parms->GetError() != SAVEGAME_E_INSUFFICIENT_ROOM ) {
// if savegame completeed we can clear retry info
GetSaveGameManager().ClearRetryInfo();
}
// Only turn off the indicator if we're not also going to save the profile settings
if ( master != NULL && master->GetProfile() != NULL && !master->GetProfile()->IsDirty() ) {
common->Dialog().ShowSaveIndicator( false );
}
if ( parms->GetError() == SAVEGAME_E_NONE ) {
// Save the profile any time we save the game
if ( master != NULL && master->GetProfile() != NULL ) {
master->GetProfile()->SaveSettings( false );
}
// Update the enumerated savegames
saveGameDetailsList_t & detailList = session->GetSaveGameManager().GetEnumeratedSavegamesNonConst();
idSaveGameDetails * details = detailList.Find( parms->description );
if ( details == NULL ) {
// add it
detailList.Append( parms->description );
} else {
// replace it
*details = parms->description;
}
}
// Error handling and additional processing
common->OnSaveCompleted( *parms );
}
/*
========================
idSessionLocal::LoadGameSync
We still want to use the savegame manager because we could have file system operations in flight and need to
========================
*/
saveGameHandle_t idSessionLocal::LoadGameSync( const char * name, saveFileEntryList_t & files ) {
idSaveLoadParms & parms = processorLoadFiles->GetParmsNonConst();
saveGameHandle_t handle = 0;
{
// Put in a local block so everything will go in the global heap before the map change, but the heap is
// automatically popped out on early return or exception
// You cannot be in the global heap during a map change...
//idScopedGlobalHeap everythingGoesInTheGlobalHeap;
// Done this way so we know it will be shutdown properly on early exit or exception
struct local_t {
local_t( idSaveLoadParms * parms_ ) : parms( parms_ ) {
// Prepare background renderer or loadscreen with what you want to show
{
// with mode: SAVE_GAME_MODE_LOAD
}
}
~local_t() {
// Shutdown background renderer or loadscreen
{
}
common->OnLoadCompleted( *parms );
}
idSaveLoadParms * parms;
} local( &parms );
// Read the details file when loading games
saveFileEntryList_t filesWithDetails( files );
std::auto_ptr< idFile_SaveGame > gameDetailsFile( new (TAG_SAVEGAMES) idFile_SaveGame( SAVEGAME_DETAILS_FILENAME, SAVEGAMEFILE_TEXT ) );
filesWithDetails.Append( gameDetailsFile.get() );
// Check the cached save details from the enumeration and make sure we don't load a save from a newer version of the game!
const saveGameDetailsList_t details = GetSaveGameManager().GetEnumeratedSavegames();
for ( int i = 0; i < details.Num(); ++i ) {
if ( idStr::Cmp( name, details[i].slotName ) == 0 ) {
if ( details[i].GetSaveVersion() > BUILD_NUMBER ) {
parms.errorCode = SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION;
return 0;
}
}
}
// Synchronous load
if ( processorLoadFiles->InitLoadFiles( name, filesWithDetails ) ) {
handle = GetSaveGameManager().ExecuteProcessorAndWait( processorLoadFiles );
}
if ( handle == 0 ) {
parms.errorCode = SAVEGAME_E_UNKNOWN;
}
if ( parms.GetError() != SAVEGAME_E_NONE ) {
return 0;
}
// Checks the description file to see if corrupted or if it's from a newer savegame
if ( !LoadGameCheckDescriptionFile( parms ) ) {
return 0;
}
// Checks to see if loaded map is from a DLC map and if that DLC is active
if ( !IsDLCAvailable( parms.description.GetMapName() ) ) {
parms.errorCode = SAVEGAME_E_DLC_NOT_FOUND;
return 0;
}
}
common->OnLoadFilesCompleted( parms );
return handle;
}
/*
========================
idSessionLocal::OnLoadCompleted
========================
*/
void idSessionLocal::OnLoadCompleted( idSaveLoadParms * parms ) {
}
/*
========================
idSessionLocal::EnumerateSaveGamesSync
========================
*/
saveGameHandle_t idSessionLocal::EnumerateSaveGamesSync() {
saveGameHandle_t handle = 0;
// Done this way so we know it will be shutdown properly on early exit or exception
struct local_t {
local_t() {
// Prepare background renderer or loadscreen with what you want to show
{
// with mode: SAVE_GAME_MODE_ENUMERATE
}
}
~local_t() {
// Shutdown background renderer or loadscreen
{
}
}
} local;
// flush the old enumerated list
GetSaveGameManager().GetEnumeratedSavegamesNonConst().Clear();
if ( processorEnumerate->Init() ) {
processorEnumerate->AddCompletedCallback( MakeCallback( this, &idSessionLocal::OnEnumerationCompleted, &processorEnumerate->GetParmsNonConst() ) );
handle = GetSaveGameManager().ExecuteProcessorAndWait( processorEnumerate );
}
// Errors within the process of saving are handled in OnEnumerationCompleted()
// so that asynchronous save errors are handled the same was as synchronous.
if ( handle == 0 ) {
idSaveLoadParms & parms = processorEnumerate->GetParmsNonConst();
parms.errorCode = SAVEGAME_E_UNKNOWN;
// Uniform error handling
OnEnumerationCompleted( &parms );
}
return handle;
}
/*
========================
idSessionLocal::EnumerateSaveGamesAsync
========================
*/
saveGameHandle_t idSessionLocal::EnumerateSaveGamesAsync() {
saveGameHandle_t handle = 0;
// flush the old enumerated list
GetSaveGameManager().GetEnumeratedSavegamesNonConst().Clear();
if ( processorEnumerate->Init() ) {
processorEnumerate->AddCompletedCallback( MakeCallback( this, &idSessionLocal::OnEnumerationCompleted, &processorEnumerate->GetParmsNonConst() ) );
handle = GetSaveGameManager().ExecuteProcessor( processorEnumerate );
}
// Errors within the process of saving are handled in OnEnumerationCompleted()
// so that asynchronous save errors are handled the same was as synchronous.
if ( handle == 0 ) {
idSaveLoadParms & parms = processorEnumerate->GetParmsNonConst();
parms.errorCode = SAVEGAME_E_UNKNOWN;
// Uniform error handling
OnEnumerationCompleted( &parms );
}
return handle;
}
int idSort_EnumeratedSavegames( const idSaveGameDetails * a, const idSaveGameDetails * b ) {
return b->date - a->date;
}
/*
========================
idSessionLocal::OnEnumerationCompleted
========================
*/
void idSessionLocal::OnEnumerationCompleted( idSaveLoadParms * parms ) {
// idTech4 idList::sort is just a qsort wrapper, which doesn't deal with
// idStrStatic properly!
// parms->detailList.Sort( idSort_EnumeratedSavegames );
std::sort( parms->detailList.Ptr(), parms->detailList.Ptr() + parms->detailList.Num() );
if ( parms->GetError() == SAVEGAME_E_NONE ) {
// Copy into the maintained list
saveGameDetailsList_t & detailsList = session->GetSaveGameManager().GetEnumeratedSavegamesNonConst();
//mem.PushHeap();
detailsList = parms->detailList; // copies new list into the savegame manager's reference
//mem.PopHeap();
// The platform-specific implementations don't know about the prefixes
// If we don't do this here, we will end up with slots like: GAME-GAME-GAME-GAME-AUTOSAVE...
for ( int i = 0; i < detailsList.Num(); i++ ) {
idSaveGameDetails & details = detailsList[i];
const idStr original = details.slotName;
const idStr stripped = RemoveSaveFolderPrefix( original, idSaveGameManager::PACKAGE_GAME );
details.slotName = stripped;
}
if ( saveGame_verbose.GetBool() ) {
OutputDetailList( detailsList );
}
}
common->OnEnumerationCompleted( *parms );
}
/*
========================
idSessionLocal::DeleteSaveGameSync
========================
*/
saveGameHandle_t idSessionLocal::DeleteSaveGameSync( const char * name ) {
saveGameHandle_t handle = 0;
// Done this way so we know it will be shutdown properly on early exit or exception
struct local_t {
local_t() {
// Prepare background renderer or loadscreen with what you want to show
{
// with mode: SAVE_GAME_MODE_DELETE
}
}
~local_t() {
// Shutdown background renderer or loadscreen
{
}
}
} local;
if ( processorDelete->InitDelete( name ) ) {
processorDelete->AddCompletedCallback( MakeCallback( this, &idSessionLocal::OnDeleteCompleted, &processorDelete->GetParmsNonConst() ) );
handle = GetSaveGameManager().ExecuteProcessorAndWait( processorDelete );
}
// Errors within the process of saving are handled in OnDeleteCompleted()
// so that asynchronous save errors are handled the same was as synchronous.
if ( handle == 0 ) {
idSaveLoadParms & parms = processorDelete->GetParmsNonConst();
parms.errorCode = SAVEGAME_E_UNKNOWN;
// Uniform error handling
OnDeleteCompleted( &parms );
}
return handle;
}
/*
========================
idSessionLocal::DeleteSaveGameAsync
========================
*/
saveGameHandle_t idSessionLocal::DeleteSaveGameAsync( const char * name ) {
saveGameHandle_t handle = 0;
if ( processorDelete->InitDelete( name ) ) {
processorDelete->AddCompletedCallback( MakeCallback( this, &idSessionLocal::OnDeleteCompleted, &processorDelete->GetParmsNonConst() ) );
common->Dialog().ShowSaveIndicator( true );
handle = GetSaveGameManager().ExecuteProcessor( processorDelete );
}
// Errors within the process of saving are handled in OnDeleteCompleted()
// so that asynchronous save errors are handled the same was as synchronous.
if ( handle == 0 ) {
idSaveLoadParms & parms = processorDelete->GetParmsNonConst();
parms.errorCode = SAVEGAME_E_UNKNOWN;
// Uniform error handling
OnDeleteCompleted( &parms );
}
return handle;
}
/*
========================
idSessionLocal::OnDeleteCompleted
========================
*/
void idSessionLocal::OnDeleteCompleted( idSaveLoadParms * parms ) {
common->Dialog().ShowSaveIndicator( false );
if ( parms->GetError() == SAVEGAME_E_NONE ) {
// Update the enumerated list
saveGameDetailsList_t & details = session->GetSaveGameManager().GetEnumeratedSavegamesNonConst();
details.Remove( parms->description );
}
common->OnDeleteCompleted( *parms );
}
/*
========================
idSessionLocal::IsEnumerating
========================
*/
bool idSessionLocal::IsEnumerating() const {
return !session->IsSaveGameCompletedFromHandle( processorEnumerate->GetHandle() );
}
/*
========================
idSessionLocal::GetEnumerationHandle
========================
*/
saveGameHandle_t idSessionLocal::GetEnumerationHandle() const {
return processorEnumerate->GetHandle();
}
/*
========================
idSessionLocal::IsDLCAvailable
========================
*/
bool idSessionLocal::IsDLCAvailable( const char * mapName ) {
bool hasContentPackage = true;
return hasContentPackage;
}
/*
========================
idSessionLocal::LoadGameCheckDiscNumber
========================
*/
bool idSessionLocal::LoadGameCheckDiscNumber( idSaveLoadParms & parms ) {
#if 0
idStr mapName = parms.description.GetMapName();
assert( !discSwapStateMgr->IsWorking() );
discSwapStateMgr->Init( &parms.callbackSignal, idDiscSwapStateManager::DISC_SWAP_COMMAND_LOAD );
//// TODO_KC this is probably broken now...
//discSwapStateMgr->folder = folder;
//discSwapStateMgr->spawnInfo = newSpawnInfo;
//discSwapStateMgr->spawnSpot = newSpawnPoint;
//discSwapStateMgr->instanceFileName = instanceFileName;
discSwapStateMgr->user = session->GetSignInManager().GetMasterLocalUser();
discSwapStateMgr->map = mapName;
discSwapStateMgr->Pump();
while ( discSwapStateMgr->IsWorking() ) {
Sys_Sleep( 15 );
// process input and render
discSwapStateMgr->Pump();
}
idDiscSwapStateManager::discSwapStateError_t discSwapError = discSwapStateMgr->GetError();
if ( discSwapError == idDiscSwapStateManager::DSSE_CANCEL ) {
parms.errorCode = SAVEGAME_E_CANCELLED;
} else if ( discSwapError == idDiscSwapStateManager::DSSE_INSUFFICIENT_ROOM ) {
parms.errorCode = SAVEGAME_E_INSUFFICIENT_ROOM;
parms.requiredSpaceInBytes = discSwapStateMgr->GetRequiredStorageBytes();
} else if ( discSwapError == idDiscSwapStateManager::DSSE_CORRECT_DISC_ALREADY ) {
parms.errorCode = SAVEGAME_E_NONE;
} else if ( discSwapError != idDiscSwapStateManager::DSSE_OK ) {
parms.errorCode = SAVEGAME_E_DISC_SWAP;
}
if ( parms.errorCode == SAVEGAME_E_UNKNOWN ) {
parms.errorCode = SAVEGAME_E_DISC_SWAP;
}
#endif
return ( parms.GetError() == SAVEGAME_E_NONE );
}
/*
========================
idSessionLocal::LoadGameCheckDescriptionFile
========================
*/
bool idSessionLocal::LoadGameCheckDescriptionFile( idSaveLoadParms & parms ) {
idFile_SaveGame ** detailsFile = FindFromGenericPtr( parms.files, SAVEGAME_DETAILS_FILENAME );
if ( detailsFile == NULL ) {
parms.errorCode = SAVEGAME_E_FILE_NOT_FOUND;
return false;
}
assert( *detailsFile != NULL );
(*detailsFile)->MakeReadOnly();
if ( !SavegameReadDetailsFromFile( *detailsFile, parms.description ) ) {
parms.errorCode = SAVEGAME_E_CORRUPTED;
} else {
if ( parms.description.GetSaveVersion() > BUILD_NUMBER ) {
parms.errorCode = SAVEGAME_E_INCOMPATIBLE_NEWER_VERSION;
}
}
return ( parms.GetError() == SAVEGAME_E_NONE );
}
#pragma region COMMANDS
/*
================================================================================================
COMMANDS
================================================================================================
*/
CONSOLE_COMMAND( testSavegameDeleteAll, "delete all savegames without confirmation", 0 ) {
if ( session == NULL ) {
idLib::Printf( "Invalid session.\n" );
return;
}
idSaveLoadParms parms;
parms.SetDefaults();
parms.mode = SAVEGAME_MBF_DELETE_ALL_FOLDERS | SAVEGAME_MBF_NO_COMPRESS;
Sys_ExecuteSavegameCommandAsync( &parms );
parms.callbackSignal.Wait();
idLib::Printf( "Completed process.\n" );
idLib::Printf( "Error = 0x%08X, %s\n", parms.GetError(), GetSaveGameErrorString( parms.GetError() ).c_str() );
}
CONSOLE_COMMAND( testSavegameDelete, "deletes a savegames without confirmation", 0 ) {
if ( session == NULL ) {
idLib::Printf( "Invalid session.\n" );
return;
}
if ( args.Argc() != 2 ) {
idLib::Printf( "Usage: testSavegameDelete <folder (without 'GAMES-')>\n" );
return;
}
idStr folder = args.Argv( 1 );
idSaveGameProcessorDelete testDeleteSaveGamesProc;
if ( testDeleteSaveGamesProc.InitDelete( folder ) ) {
session->GetSaveGameManager().ExecuteProcessorAndWait( &testDeleteSaveGamesProc );
}
idLib::Printf( "Completed process.\n" );
idLib::Printf( "Error = 0x%08X, %s\n", testDeleteSaveGamesProc.GetParms().GetError(), GetSaveGameErrorString( testDeleteSaveGamesProc.GetParms().GetError() ).c_str() );
}
CONSOLE_COMMAND( testSavegameEnumerateFiles, "enumerates all the files in a folder (blank for 'current slot' folder, use 'autosave' for the autosave slot)", 0 ) {
if ( session == NULL ) {
idLib::Printf( "Invalid session.\n" );
return;
}
idStr folder = session->GetCurrentSaveSlot();
if ( args.Argc() > 1 ) {
folder = args.Argv( 1 );
}
idLib::Printf( "Testing folder: %s\n\n", folder.c_str() );
idSaveLoadParms parms;
parms.SetDefaults();
parms.mode = SAVEGAME_MBF_ENUMERATE_FILES;
// Platform-specific implementation
// This will start a worker thread for async operation.
// It will always signal when it's completed.
Sys_ExecuteSavegameCommandAsync( &parms );
parms.callbackSignal.Wait();
for ( int i = 0; i < parms.files.Num(); i++ ) {
idLib::Printf( S_COLOR_YELLOW "\t%d: %s\n" S_COLOR_DEFAULT, i, parms.files[i]->GetName() );
}
}
/*
========================
OutputDetailList
========================
*/
void OutputDetailList( const saveGameDetailsList_t & savegameList ) {
for ( int i = 0; i < savegameList.Num(); ++i ) {
idLib::Printf( S_COLOR_YELLOW "\t%s - %s\n" S_COLOR_DEFAULT
"\t\tMap: %s\n"
"\t\tTime: %s\n",
savegameList[i].slotName.c_str(),
savegameList[i].damaged ? S_COLOR_RED "CORRUPT" : S_COLOR_GREEN "OK",
savegameList[i].damaged ? "?" : savegameList[i].descriptors.GetString( SAVEGAME_DETAIL_FIELD_MAP, "" ),
Sys_TimeStampToStr( savegameList[i].date )
);
}
}
CONSOLE_COMMAND( testSavegameEnumerate, "enumerates the savegames available", 0 ) {
if ( session == NULL ) {
idLib::Printf( "Invalid session.\n" );
return;
}
saveGameHandle_t handle = session->EnumerateSaveGamesSync();
if ( handle == 0 ) {
idLib::Printf( "Error enumerating.\n" );
return;
}
const saveGameDetailsList_t gameList = session->GetSaveGameManager().GetEnumeratedSavegames();
idLib::Printf( "Savegames found: %d\n\n", gameList.Num() );
OutputDetailList( gameList );
}
CONSOLE_COMMAND( testSaveGameCheck, "tests existence of savegame", 0 ) {
bool exists;
bool autosaveExists;
Sys_SaveGameCheck( exists, autosaveExists );
idLib::Printf( "Savegame check: exists = %d, autosaveExists = %d\n", exists, autosaveExists );
}
CONSOLE_COMMAND( testSaveGameOutputEnumeratedSavegames, "outputs the list of savegames already enumerated, this does not re-enumerate", 0 ) {
if ( session == NULL ) {
idLib::Printf( "Invalid session.\n" );
return;
}
const saveGameDetailsList_t & savegames = session->GetSaveGameManager().GetEnumeratedSavegames();
OutputDetailList( savegames );
}
CONSOLE_COMMAND( testSavegameGetCurrentSlot, "returns the current slot in use", 0 ) {
if ( session == NULL ) {
idLib::Printf( "Invalid session.\n" );
return;
}
idLib::Printf( "Current slot: %s\n", session->GetCurrentSaveSlot() );
}
CONSOLE_COMMAND( testSavegameSetCurrentSlot, "returns the current slot in use", 0 ) {
if ( session == NULL ) {
idLib::Printf( "Invalid session.\n" );
return;
}
if ( args.Argc() != 2 ) {
idLib::Printf( "Usage: testSavegameSetCurrentSlot name\n" );
return;
}
const char * slot = args.Argv( 1 );
session->SetCurrentSaveSlot( slot );
idLib::Printf( "Current slot: %s\n", session->GetCurrentSaveSlot() );
}
CONSOLE_COMMAND( savegameSetErrorBit, "Allows you to set savegame_error by bit instead of integer value", 0 ) {
int bit = atoi( args.Argv( 1 ) );
savegame_error.SetInteger( savegame_error.GetInteger() | ( 1 << bit ) );
}
#pragma endregion
+88
View File
@@ -0,0 +1,88 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_SESSION_SAVEGAMES_H__
#define __SYS_SESSION_SAVEGAMES_H__
/*
================================================
idSaveGameProcessorLoadFiles
================================================
*/
class idSaveGameProcessorLoadFiles : public idSaveGameProcessor {
public:
DEFINE_CLASS( idSaveGameProcessorLoadFiles );
virtual bool InitLoadFiles( const char * folder,
const saveFileEntryList_t & files,
idSaveGameManager::packageType_t type = idSaveGameManager::PACKAGE_GAME );
virtual bool Process();
};
/*
================================================win_sav
idSaveGameProcessorDelete
================================================
*/
class idSaveGameProcessorDelete : public idSaveGameProcessor {
public:
DEFINE_CLASS( idSaveGameProcessorDelete );
bool InitDelete( const char * folder, idSaveGameManager::packageType_t type = idSaveGameManager::PACKAGE_GAME );
virtual bool Process();
};
/*
================================================
idSaveGameProcessorSaveFiles
================================================
*/
class idSaveGameProcessorSaveFiles : public idSaveGameProcessor {
public:
DEFINE_CLASS( idSaveGameProcessorSaveFiles );
// Passing in idSaveGameDetails so that we have a copy on output
bool InitSave( const char * folder,
const saveFileEntryList_t & files,
const idSaveGameDetails & description,
idSaveGameManager::packageType_t type = idSaveGameManager::PACKAGE_GAME );
virtual bool Process();
};
/*
================================================
idSaveGameProcessorEnumerateGames
================================================
*/
class idSaveGameProcessorEnumerateGames : public idSaveGameProcessor {
public:
DEFINE_CLASS( idSaveGameProcessorEnumerateGames );
virtual bool Process();
};
#endif
+249
View File
@@ -0,0 +1,249 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
idCVar com_requireNonProductionSignIn( "com_requireNonProductionSignIn", "1", CVAR_BOOL|CVAR_ARCHIVE, "If true, will require sign in, even on non production builds." );
extern idCVar fs_savepath;
extern idCVar g_demoMode;
/*
========================
idSignInManagerBase::ProcessInputEvent
========================
*/
bool idSignInManagerBase::ProcessInputEvent( const sysEvent_t * ev ) {
// If we could use more local users, poll for them
if ( GetNumLocalUsers() < maxDesiredLocalUsers && !IsAnyDeviceBeingRegistered() ) {
if ( ev->IsKeyDown() ) {
if ( ev->GetKey() == K_JOY1 || ev->GetKey() == K_JOY9 || ev->GetKey() == K_ENTER || ev->GetKey() == K_KP_ENTER ) {
// Register a local user so they can use this input device only done for demos press start screen
// otherwise the user is registered when selecting which game to play.
}
}
}
return false;
}
/*
========================
idSignInManagerBase::GetDefaultProfile
========================
*/
idPlayerProfile * idSignInManagerBase::GetDefaultProfile() {
if ( defaultProfile == NULL ) {
// Create a new profile
defaultProfile = idPlayerProfile::CreatePlayerProfile( 0 );
}
return defaultProfile;
}
/*
========================
idSignInManagerBase::GetLocalUserByInputDevice
========================
*/
idLocalUser * idSignInManagerBase::GetLocalUserByInputDevice( int index ) {
for ( int i = 0; i < GetNumLocalUsers(); i++ ) {
if ( GetLocalUserByIndex( i )->GetInputDevice() == index ) {
return GetLocalUserByIndex( i ); // Found it
}
}
return NULL; // Not found
}
/*
========================
idSignInManagerBase::GetLocalUserByHandle
========================
*/
idLocalUser * idSignInManagerBase::GetLocalUserByHandle( localUserHandle_t handle ) {
for ( int i = 0; i < GetNumLocalUsers(); i++ ) {
if ( GetLocalUserByIndex( i )->GetLocalUserHandle() == handle ) {
return GetLocalUserByIndex( i ); // Found it
}
}
return NULL; // Not found
}
/*
========================
idSignInManagerBase::GetPlayerProfileByInputDevice
========================
*/
idPlayerProfile * idSignInManagerBase::GetPlayerProfileByInputDevice( int index ) {
idLocalUser * user = session->GetSignInManager().GetLocalUserByInputDevice( index );
idPlayerProfile * profile = NULL;
if ( user != NULL ) {
profile = user->GetProfile();
}
return profile;
}
/*
========================
idSignInManagerBase::RemoveLocalUserByInputDevice
========================
*/
bool idSignInManagerBase::RemoveLocalUserByInputDevice( int index ) {
for ( int i = 0; i < GetNumLocalUsers(); i++ ) {
if ( GetLocalUserByIndex( i )->GetInputDevice() == index ) {
RemoveLocalUserByIndex( i );
return true;
}
}
return false; // Not found
}
/*
========================
idSignInManagerBase::RemoveLocalUserByHandle
========================
*/
bool idSignInManagerBase::RemoveLocalUserByHandle( localUserHandle_t handle ) {
for ( int i = 0; i < GetNumLocalUsers(); i++ ) {
if ( GetLocalUserByIndex( i )->GetLocalUserHandle() == handle ) {
RemoveLocalUserByIndex( i );
return true;
}
}
return false; // Not found
}
/*
========================
idSignInManagerBase::SaveUserProfiles
========================
*/
void idSignInManagerBase::SaveUserProfiles() {
for ( int i = 0; i < GetNumLocalUsers(); i++ ) {
idLocalUser * localUser = GetLocalUserByIndex( i );
if ( localUser != NULL ) {
idPlayerProfile * profile = localUser->GetProfile();
if ( profile != NULL ) {
profile->SaveSettings( false );
}
}
}
}
/*
========================
idSignInManagerBase::RemoveAllLocalUsers
========================
*/
void idSignInManagerBase::RemoveAllLocalUsers() {
while ( GetNumLocalUsers() > 0 ) {
RemoveLocalUserByIndex( 0 );
}
}
/*
========================
idSignInManagerBase::ValidateLocalUsers
========================
*/
void idSignInManagerBase::ValidateLocalUsers( bool requireOnline ) {
if ( !RequirePersistentMaster() ) {
return;
}
for ( int i = GetNumLocalUsers() - 1; i >= 0; i-- ) {
idLocalUser * localUser = GetLocalUserByIndex( i );
// If this user does not have a profile, remove them.
// If this user is not online-capable and we require online, remove them.
if ( !localUser->IsProfileReady() ||
( requireOnline && ( !localUser->IsOnline() || !localUser->CanPlayOnline() ) ) ) {
RemoveLocalUserByIndex( i );
}
}
}
/*
========================
idSignInManagerBase::RequirePersistentMaster
========================
*/
bool idSignInManagerBase::RequirePersistentMaster() {
#ifdef ID_RETAIL
return true; // ALWAYS require persistent master on retail builds
#else
// Non retail production builds require a persistent profile unless si_splitscreen is set
extern idCVar si_splitscreen;
// If we are forcing splitscreen, then we won't require a profile (this is for development)
if ( si_splitscreen.GetInteger() != 0 ) {
return false;
}
return com_requireNonProductionSignIn.GetBool();
#endif
}
/*
========================
idSignInManagerBase::GetUniqueLocalUserHandle
Uniquely generate a handle based on name and time
========================
*/
localUserHandle_t idSignInManagerBase::GetUniqueLocalUserHandle( const char * name ) {
MD5_CTX ctx;
unsigned char digest[16];
int64 clockTicks = Sys_GetClockTicks();
MD5_Init( &ctx );
MD5_Update( &ctx, (const unsigned char *)name, idStr::Length( name ) );
MD5_Update( &ctx, (const unsigned char *)&clockTicks, sizeof( clockTicks ) );
MD5_Final( &ctx, (unsigned char *)digest );
// Quantize the 128 bit hash down to the number of bits needed for a localUserHandle_t
const int STRIDE_BYTES = sizeof( localUserHandle_t::userHandleType_t );
const int NUM_LOOPS = 16 / STRIDE_BYTES;
localUserHandle_t::userHandleType_t handle = 0;
for ( int i = 0; i < NUM_LOOPS; i++ ) {
localUserHandle_t::userHandleType_t tempHandle = 0;
for ( int j = 0; j < STRIDE_BYTES; j++ ) {
tempHandle |= ( ( localUserHandle_t::userHandleType_t )digest[ ( i * STRIDE_BYTES ) + j ] ) << ( j * 8 );
}
handle ^= tempHandle;
}
return localUserHandle_t( handle );
}
+99
View File
@@ -0,0 +1,99 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_SIGNIN_H__
#define __SYS_SIGNIN_H__
/*
================================================
idSignInManagerBase
================================================
*/
class idSignInManagerBase {
public:
idSignInManagerBase() :
minDesiredLocalUsers( 0 ),
maxDesiredLocalUsers( 0 ),
defaultProfile( NULL ) {}
virtual ~idSignInManagerBase() {}
virtual void Pump() = 0;
virtual int GetNumLocalUsers() const = 0;
virtual idLocalUser * GetLocalUserByIndex( int index ) = 0;
virtual const idLocalUser * GetLocalUserByIndex( int index ) const = 0;
virtual void RemoveLocalUserByIndex( int index ) = 0;
virtual void RegisterLocalUser( int inputDevice ) = 0; // Register a local controller user to the passed in input device
virtual idLocalUser * GetRegisteringUser() { return NULL; } // This is a user that has started the registration process but is not yet a local user.
virtual idLocalUser * GetRegisteringUserByInputDevice( int inputDevice ) { return NULL; }
virtual void SignIn() {}
virtual bool IsDeviceBeingRegistered( int intputDevice ) { return false; }
virtual bool IsAnyDeviceBeingRegistered() { return false; }
virtual void Shutdown() {}
// Outputs all the local users and other debugging information from the sign in manager
virtual void DebugOutputLocalUserInfo() {}
//================================================================================
// Common helper functions
//================================================================================
void SetDesiredLocalUsers( int minDesiredLocalUsers, int maxDesiredLocalUsers ) { this->minDesiredLocalUsers = minDesiredLocalUsers; this->maxDesiredLocalUsers = maxDesiredLocalUsers; }
bool ProcessInputEvent( const sysEvent_t * ev );
idPlayerProfile * GetDefaultProfile();
// Master user always index 0
idLocalUser * GetMasterLocalUser() { return ( GetNumLocalUsers() > 0 ) ? GetLocalUserByIndex( 0 ) : NULL; }
const idLocalUser * GetMasterLocalUser() const { return ( GetNumLocalUsers() > 0 ) ? GetLocalUserByIndex( 0 ) : NULL; }
bool IsMasterLocalUserPersistent() const { return ( GetMasterLocalUser() != NULL ) ? GetMasterLocalUser()->IsPersistent() : false; }
bool IsMasterLocalUserOnline() const { return ( GetMasterLocalUser() != NULL ) ? GetMasterLocalUser()->IsOnline() : false; }
int GetMasterInputDevice() const { return ( GetMasterLocalUser() != NULL ) ? GetMasterLocalUser()->GetInputDevice() : -1; }
localUserHandle_t GetMasterLocalUserHandle() const { return ( GetMasterLocalUser() != NULL ) ? GetMasterLocalUser()->GetLocalUserHandle() : localUserHandle_t(); }
idLocalUser * GetLocalUserByInputDevice( int index );
idLocalUser * GetLocalUserByHandle( localUserHandle_t handle );
idPlayerProfile * GetPlayerProfileByInputDevice( int index );
bool RemoveLocalUserByInputDevice( int index );
bool RemoveLocalUserByHandle( localUserHandle_t handle );
void RemoveAllLocalUsers();
void SaveUserProfiles();
// This will remove local players that are not signed into a profile.
// If requiredOnline: This removes the users who cannot play online
void ValidateLocalUsers( bool requireOnline );
bool RequirePersistentMaster();
localUserHandle_t GetUniqueLocalUserHandle( const char * name );
protected:
int minDesiredLocalUsers;
int maxDesiredLocalUsers;
idPlayerProfile * defaultProfile;
};
#endif // __SYS_SIGNIN_H__
+91
View File
@@ -0,0 +1,91 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_STATS_H__
#define __SYS_STATS_H__
//------------------------
// leaderboardError_t
//------------------------
enum leaderboardError_t {
LEADERBOARD_ERROR_NONE,
LEADERBOARD_ERROR_FAILED, // General error occurred
LEADERBOARD_ERROR_NOT_ONLINE, // Not online to request leaderboards
LEADERBOARD_ERROR_BUSY, // Unable to download leaderboards right now (download already in progress)
LEADERBOARD_ERROR_INVALID_USER, // Unable to request leaderboards as the given user
LEADERBOARD_ERROR_INVALID_REQUEST, // The leaderboard request was invalid
LEADERBOARD_ERROR_DOWNLOAD, // An error occurred while downloading the leaderboard
LEADERBOARD_ERROR_MAX
};
/*
================================================
idLeaderboardCallback
================================================
*/
class idLeaderboardCallback : public idCallback {
public:
struct row_t {
bool hasAttachment;
int64 attachmentID;
idStr name;
int64 rank;
idArray< int64, MAX_LEADERBOARD_COLUMNS > columns;
};
idLeaderboardCallback() : def( NULL ), startIndex( -1 ), localIndex( -1 ), numRowsInLeaderboard( -1 ), errorCode( LEADERBOARD_ERROR_NONE ) { }
virtual idLeaderboardCallback * Clone() const = 0;
// Used by the platform handlers to set data
void ResetRows() { rows.Clear(); }
void AddRow( const row_t & row ) { rows.Append( row ); }
void SetNumRowsInLeaderboard( int32 i ) { numRowsInLeaderboard = i; }
void SetDef( const leaderboardDefinition_t * def_ ) { def = def_; }
void SetStartIndex( int startIndex_ ) { startIndex = startIndex_; }
void SetLocalIndex( int localIndex_ ) { localIndex = localIndex_; }
void SetErrorCode( leaderboardError_t errorCode ) { this->errorCode = errorCode; }
// Used in user callback for information retrieval
const leaderboardDefinition_t * GetDef() const { return def; }
int GetStartIndex() const { return startIndex; }
const idList< row_t > & GetRows() const { return rows; }
int GetNumRowsInLeaderboard() const { return numRowsInLeaderboard; }
int GetLocalIndex() const { return localIndex; }
leaderboardError_t GetErrorCode() const { return this->errorCode; }
protected:
const leaderboardDefinition_t * def; // leaderboard def
int startIndex; // where the first row starts in the online leaderboard
int localIndex; // if player is in the rows, this is the offset of him within the returned rows
idList< row_t > rows; // leaderboard entries for the request
int numRowsInLeaderboard; // total number of rows in the online leaderboard
leaderboardError_t errorCode; // error, if any, that occurred during last operation
};
#endif // !__SYS_STATS_H__
+147
View File
@@ -0,0 +1,147 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_STATS_MISC_H__
#define __SYS_STATS_MISC_H__
/*
================================================================================================
Stats (for achievements, matchmaking, etc.)
================================================================================================
*/
/*
================================================
systemStats_t
This is to give the framework the ability to deal with stat indexes that are
completely up to each game. The system needs to deal with some stat indexes for things like
the level for matchmaking, etc.
================================================
*/
/*
================================================================================================
Leader Boards
================================================================================================
*/
const int MAX_LEADERBOARDS = 256;
const int MAX_LEADERBOARD_COLUMNS = 16;
enum aggregationMethod_t {
AGGREGATE_MIN, // Write the new value if it is less than the existing value.
AGGREGATE_MAX, // Write the new value if it is greater than the existing value.
AGGREGATE_SUM, // Add the new value to the existing value and write the result.
AGGREGATE_LAST, // Write the new value.
};
enum rankOrder_t {
RANK_GREATEST_FIRST, // Rank the in descending order, greatest score is best score
RANK_LEAST_FIRST, // Rank the in ascending order, lowest score is best score
};
enum statsColumnDisplayType_t {
STATS_COLUMN_DISPLAY_NUMBER,
STATS_COLUMN_DISPLAY_TIME_MILLISECONDS,
STATS_COLUMN_DISPLAY_CASH,
STATS_COLUMN_NEVER_DISPLAY,
};
struct columnDef_t {
const char * locDisplayName;
int bits;
aggregationMethod_t aggregationMethod;
statsColumnDisplayType_t displayType;
};
struct leaderboardDefinition_t {
leaderboardDefinition_t() :
id ( -1 ),
numColumns( 0 ),
columnDefs( NULL ),
rankOrder( RANK_GREATEST_FIRST ),
supportsAttachments( false ),
checkAgainstCurrent( false ) {
}
leaderboardDefinition_t( int id_, int numColumns_, const columnDef_t * columnDefs_, rankOrder_t rankOrder_, bool supportsAttachments_, bool checkAgainstCurrent_ ) :
id ( id_ ),
numColumns( numColumns_ ),
columnDefs( columnDefs_ ),
rankOrder( rankOrder_ ),
supportsAttachments( supportsAttachments_ ),
checkAgainstCurrent( checkAgainstCurrent_ ) {
}
int32 id;
int32 numColumns;
const columnDef_t * columnDefs;
rankOrder_t rankOrder;
bool supportsAttachments;
bool checkAgainstCurrent; // Compare column 0 with the currently stored leaderboard, and only submit the new leaderboard if the new column 0 is better
idStr boardName; // Only used for display name within steam. If Empty, will generate. must be specifically set.
};
struct column_t {
column_t( int64 value_ ) : value( value_ ) {}
column_t() {}
int64 value;
};
/*
================================================================================================
Contains the Achievement and LeaderBoard free function declarations.
================================================================================================
*/
typedef int32 leaderboardHandle_t;
/*
================================================
idLeaderBoardEntry
================================================
*/
class idLeaderBoardEntry {
public:
static const int MAX_LEADERBOARD_COLUMNS = 16;
idStr username; // aka gamertag
int64 score;
int64 columns[ MAX_LEADERBOARD_COLUMNS ];
};
const leaderboardDefinition_t * Sys_FindLeaderboardDef( int id );
leaderboardDefinition_t * Sys_CreateLeaderboardDef( int id_, int numColumns_, const columnDef_t * columnDefs_, rankOrder_t rankOrder_, bool supportsAttachments_, bool checkAgainstCurrent_ );
void Sys_DestroyLeaderboardDefs();
#endif // !__SYS_STATS_MISC_H__
+593
View File
@@ -0,0 +1,593 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../idlib/precompiled.h"
#include "sys_voicechat.h"
/*
================================================
idVoiceChatMgr::Init
================================================
*/
void idVoiceChatMgr::Init( void * pXAudio2 ) {
}
/*
================================================
idVoiceChatMgr::Shutdown
================================================
*/
void idVoiceChatMgr::Shutdown() {
// We shouldn't have voice users if everything shutdown correctly
assert( talkers.Num() == 0 );
assert( remoteMachines.Num() == 0 );
}
/*
================================================
idVoiceChatMgr::RegisterTalker
================================================
*/
void idVoiceChatMgr::RegisterTalker( lobbyUser_t * user, int lobbyType, bool isLocal ) {
int i = FindTalkerIndex( user, lobbyType );
if ( !verify( i == -1 ) ) {
assert( talkers[i].lobbyType == lobbyType );
idLib::Printf( "RegisterTalker: Talker already registered.\n" );
return;
}
// Talker not found, need to create a new one
talker_t newTalker;
newTalker.user = user;
newTalker.isLocal = isLocal;
newTalker.lobbyType = lobbyType;
newTalker.registered = false;
newTalker.registeredSuccess = false;
newTalker.machineIndex = -1;
newTalker.groupIndex = 0; // 0 is default group
if ( !newTalker.IsLocal() ) { // If this is a remote talker, register his machine address
newTalker.machineIndex = AddMachine( user->address, lobbyType );
}
talkers.Append( newTalker );
// Since we added a new talker, make sure he is registered. UpdateRegisteredTalkers will catch all users, including this one.
UpdateRegisteredTalkers();
}
/*
================================================
idVoiceChatMgr::UnregisterTalker
================================================
*/
void idVoiceChatMgr::UnregisterTalker( lobbyUser_t * user, int lobbyType, bool isLocal ) {
int i = FindTalkerIndex( user, lobbyType );
if ( !verify( i != -1 ) ) {
idLib::Printf( "UnregisterTalker: Talker not found.\n" );
return;
}
talker_t & talker = talkers[i];
assert( talker.IsLocal() == ( talker.machineIndex == -1 ) );
assert( talker.IsLocal() == isLocal );
talker.lobbyType = -1; // Mark for removal
UpdateRegisteredTalkers(); // Make sure the user gets unregistered before we remove him/her
if ( talker.machineIndex != -1 ) {
// Unregister the talkers machine (unique address) handle
RemoveMachine( talker.machineIndex, lobbyType );
}
talkers.RemoveIndex( i ); // Finally, remove the talker
}
/*
================================================
idVoiceChatMgr::GetActiveLocalTalkers
================================================
*/
void idVoiceChatMgr::GetActiveLocalTalkers( idStaticList< int, MAX_PLAYERS > & localTalkers ) {
localTalkers.Clear();
for ( int i = 0; i < talkers.Num(); i++ ) {
if ( !talkers[i].IsLocal() ) {
continue;
}
if ( !talkers[i].registeredSuccess ) {
continue;
}
if ( !TalkerHasData( i ) ) {
continue;
}
localTalkers.Append( i );
}
}
/*
================================================
idVoiceChatMgr::GetRecipientsForTalker
================================================
*/
void idVoiceChatMgr::GetRecipientsForTalker( int talkerIndex, idStaticList< const lobbyAddress_t *, MAX_PLAYERS > & recipients ) {
recipients.Clear();
talker_t & talker = talkers[talkerIndex];
if ( !talker.IsLocal() ) {
return;
}
sendFrame++;
for ( int i = 0; i < talkers.Num(); i++ ) {
if ( !talkers[i].registeredSuccess ) {
continue;
}
if ( talkers[i].IsLocal() ) {
continue; // Only want to send to remote talkers
}
if ( !CanSendVoiceTo( talkerIndex, i ) ) {
continue;
}
if ( !sendGlobal && talkers[i].groupIndex != activeGroupIndex ) {
continue;
}
assert( talkers[i].machineIndex >= 0 );
remoteMachine_t & remoteMachine = remoteMachines[ talkers[i].machineIndex ];
assert( remoteMachine.refCount > 0 );
assert( remoteMachine.lobbyType == activeLobbyType );
if ( remoteMachine.sendFrame == sendFrame ) {
continue; // Already on the recipient list
}
remoteMachine.sendFrame = sendFrame;
recipients.Append( &remoteMachine.address );
}
}
/*
================================================
idVoiceChatMgr::SetTalkerGroup
================================================
*/
void idVoiceChatMgr::SetTalkerGroup( const lobbyUser_t * user, int lobbyType, int groupIndex ) {
int i = FindTalkerIndex( user, lobbyType );
if ( !verify( i != -1 ) ) {
idLib::Printf( "SetTalkerGroup: Talker not found.\n" );
return;
}
// Assign the new group index to this talker
talkers[i].groupIndex = groupIndex;
// Since the group index of this player changed, call UpdateRegisteredTalkers, which will register the
// appropriate users based on the current active group and session
UpdateRegisteredTalkers();
}
/*
================================================
idVoiceChatMgr::SetActiveLobby
================================================
*/
void idVoiceChatMgr::SetActiveLobby( int lobbyType ) {
if ( activeLobbyType != lobbyType ) {
activeLobbyType = lobbyType;
// When the active session changes, we need to immediately call UpdateRegisteredTalkers,
// which will make sure the appropriate talkers are registered depending on the activeSession.
UpdateRegisteredTalkers();
}
}
/*
================================================
idVoiceChatMgr::SetActiveChatGroup
================================================
*/
void idVoiceChatMgr::SetActiveChatGroup( int groupIndex ) {
if ( activeGroupIndex != groupIndex ) {
activeGroupIndex = groupIndex;
// When the active group changes, we need to immediately call UpdateRegisteredTalkers,
// which will make sure the appropriate talkers are registered depending on the activeGroup.
UpdateRegisteredTalkers();
}
}
/*
================================================
idVoiceChatMgr::FindTalkerByUserId
================================================
*/
int idVoiceChatMgr::FindTalkerByUserId( lobbyUserID_t userID, int lobbyType ) {
for ( int i = 0; i < talkers.Num(); i++ ) {
if ( talkers[i].user->lobbyUserID == userID && talkers[i].lobbyType == lobbyType ) {
return i;
}
}
return -1; // Not found
}
/*
================================================
idVoiceChatMgr::GetLocalChatData
================================================
*/
bool idVoiceChatMgr::GetLocalChatData( int talkerIndex, byte * data, int & dataSize ) {
talker_t & talker = talkers[talkerIndex];
if ( !talker.IsLocal() ) {
idLib::Printf( "GetLocalChatData: Talker not local.\n" );
return false; // Talker is remote
}
if ( !talker.registeredSuccess ) {
return false;
}
idBitMsg voiceMsg;
voiceMsg.InitWrite( data, dataSize );
talker.user->lobbyUserID.WriteToMsg( voiceMsg );
voiceMsg.WriteByteAlign();
// Remove the size of the userid field from the available buffer size
int voiceDataSize = dataSize - voiceMsg.GetSize();
if ( !GetLocalChatDataInternal( talkerIndex, voiceMsg.GetWriteData() + voiceMsg.GetSize(), voiceDataSize ) ) {
dataSize = 0;
return false;
}
dataSize = voiceDataSize + voiceMsg.GetSize();
// Mark the user as talking
talker.talking = true;
talker.talkingTime = Sys_Milliseconds();
return dataSize > 0 ? true : false;
}
/*
================================================
idVoiceChatMgr::SubmitIncomingChatData
================================================
*/
void idVoiceChatMgr::SubmitIncomingChatData( const byte * data, int dataSize ) {
lobbyUserID_t lobbyUserID;
idBitMsg voiceMsg;
voiceMsg.InitRead( data, dataSize );
lobbyUserID.ReadFromMsg( voiceMsg );
voiceMsg.ReadByteAlign();
int i = FindTalkerByUserId( lobbyUserID, activeLobbyType );
if ( i == -1 ) {
idLib::Printf( "SubmitIncomingChatData: Talker not found in session.\n" );
return; // Talker is not in this session
}
talker_t & talker = talkers[i];
if ( talker.registeredSuccess && !talker.isMuted ) {
// Mark the user as talking
talker.talking = true;
talker.talkingTime = Sys_Milliseconds();
SubmitIncomingChatDataInternal( i, voiceMsg.GetReadData() + voiceMsg.GetReadCount(), voiceMsg.GetRemainingData() );
}
}
/*
========================
idVoiceChatMgr::GetVoiceState
========================
*/
voiceState_t idVoiceChatMgr::GetVoiceState( const lobbyUser_t * user ) {
int i = FindTalkerByUserId( user->lobbyUserID, activeLobbyType );
if ( i == -1 ) {
return VOICECHAT_STATE_NO_MIC;
}
talker_t & talker = talkers[i];
if ( !talker.hasHeadset ) {
return VOICECHAT_STATE_NO_MIC;
}
if ( talker.isMuted ) {
return VOICECHAT_STATE_MUTED_LOCAL;
}
if ( talker.talking && Sys_Milliseconds() - talker.talkingTime > 200 ) {
talker.talking = false;
}
return talker.talking ? (talker.talkingGlobal ? VOICECHAT_STATE_TALKING_GLOBAL : VOICECHAT_STATE_TALKING ) : VOICECHAT_STATE_NOT_TALKING;
}
/*
========================
idVoiceChatMgr::CanSendVoiceTo
========================
*/
bool idVoiceChatMgr::CanSendVoiceTo( int talkerFromIndex, int talkerToIndex ) {
talker_t & talkerFrom = talkers[talkerFromIndex];
if ( !talkerFrom.IsLocal() ) {
return false;
}
talker_t & talkerTo = talkers[talkerToIndex];
if ( talkerTo.isMuted ) {
return false;
}
return true;
}
/*
========================
idVoiceChatMgr::IsRestrictedByPrivleges
========================
*/
bool idVoiceChatMgr::IsRestrictedByPrivleges() {
return ( disableVoiceReasons & REASON_PRIVILEGES ) != 0;
}
/*
========================
idVoiceChatMgr::ToggleMuteLocal
========================
*/
void idVoiceChatMgr::ToggleMuteLocal( const lobbyUser_t * src, const lobbyUser_t * target ) {
int fromTalkerIndex = FindTalkerByUserId( src->lobbyUserID, activeLobbyType );
if ( fromTalkerIndex == -1 ) {
return;
}
int toTalkerIndex = FindTalkerByUserId( target->lobbyUserID, activeLobbyType );
if ( toTalkerIndex == -1 ) {
return;
}
talker_t & targetTalker = talkers[toTalkerIndex];
targetTalker.isMuted = targetTalker.isMuted ? false : true;
}
//================================================
// **** INTERNAL **********
//================================================
/*
================================================
idVoiceChatMgr::FindTalkerIndex
================================================
*/
int idVoiceChatMgr::FindTalkerIndex( const lobbyUser_t * user, int lobbyType ) {
for ( int i = 0; i < talkers.Num(); i++ ) {
if ( talkers[i].user == user && talkers[i].lobbyType == lobbyType ) {
return i;
}
}
return -1; // Not found
}
/*
================================================
idVoiceChatMgr::FindMachine
================================================
*/
int idVoiceChatMgr::FindMachine( const lobbyAddress_t & address, int lobbyType ) {
for ( int i = 0; i < remoteMachines.Num(); i++ ) {
if ( remoteMachines[i].refCount == 0 ) {
continue;
}
if ( remoteMachines[i].lobbyType == lobbyType && remoteMachines[i].address.Compare( address ) ) {
return i;
}
}
return -1; // Not found
}
/*
================================================
idVoiceChatMgr::AddMachine
================================================
*/
int idVoiceChatMgr::AddMachine( const lobbyAddress_t & address, int lobbyType ) {
int machineIndex = FindMachine( address, lobbyType );
if ( machineIndex != -1 ) {
// If we find an existing machine, just increase the ref
remoteMachines[machineIndex].refCount++;
return machineIndex;
}
//
// We didn't find a machine, we'll need to add one
//
// First, see if there is a free machine slot to take
int index = -1;
for ( int i = 0; i < remoteMachines.Num(); i++ ) {
if ( remoteMachines[i].refCount == 0 ) {
index = i;
break;
}
}
remoteMachine_t newMachine;
newMachine.lobbyType = lobbyType;
newMachine.address = address;
newMachine.refCount = 1;
newMachine.sendFrame = -1;
if ( index == -1 ) {
// If we didn't find a machine slot, then add one
index = remoteMachines.Append( newMachine );
} else {
// Re-use the machine slot we found
remoteMachines[index] = newMachine;
}
return index;
}
/*
================================================
idVoiceChatMgr::RemoveMachine
================================================
*/
void idVoiceChatMgr::RemoveMachine( int machineIndex, int lobbyType ) {
assert( remoteMachines[machineIndex].refCount > 0 );
assert( remoteMachines[machineIndex].lobbyType == lobbyType );
// Don't remove the machine. refCount will eventually reach 0, which will free up the slot to reclaim.
// We don't want to remove it, because that would invalidate users machineIndex handles into the array
remoteMachines[machineIndex].refCount--;
}
/*
================================================
idVoiceChatMgr::UpdateRegisteredTalkers
================================================
*/
void idVoiceChatMgr::UpdateRegisteredTalkers() {
for ( int pass = 0; pass < 2; pass++ ) {
for ( int i = 0; i < talkers.Num(); i++ ) {
talker_t & talker = talkers[i];
bool shouldBeRegistered = ( talker.lobbyType != -1 && disableVoiceReasons == 0 && talker.lobbyType == activeLobbyType );
if ( shouldBeRegistered && pass == 0 ) {
continue; // Only unregister on first pass to make room for when the second pass will possibly register new talkers
}
if ( talker.registered != shouldBeRegistered ) {
if ( !talker.registered ) {
talker.registeredSuccess = RegisterTalkerInternal( i );
} else if ( talker.registeredSuccess ) {
UnregisterTalkerInternal( i );
talker.registeredSuccess = false;
}
talker.registered = shouldBeRegistered;
}
}
}
}
/*
================================================
idVoiceChatMgr::SetDisableVoiceReason
================================================
*/
void idVoiceChatMgr::SetDisableVoiceReason( disableVoiceReason_t reason ) {
if ( ( disableVoiceReasons & reason ) == 0 ) {
disableVoiceReasons |= reason;
UpdateRegisteredTalkers();
}
}
/*
================================================
idVoiceChatMgr::ClearDisableVoiceReason
================================================
*/
void idVoiceChatMgr::ClearDisableVoiceReason( disableVoiceReason_t reason ) {
if ( ( disableVoiceReasons & reason ) != 0 ) {
disableVoiceReasons &= ~reason;
UpdateRegisteredTalkers();
}
}
/*
================================================
idVoiceChatMgr::SetHeadsetState
================================================
*/
void idVoiceChatMgr::SetHeadsetState( int talkerIndex, bool state ) {
talker_t & talker = talkers[ talkerIndex ];
talker.hasHeadset = state;
}
/*
================================================
idVoiceChatMgr::HasHeadsetStateChanged
================================================
*/
bool idVoiceChatMgr::HasHeadsetStateChanged( int talkerIndex )
{
talker_t & talker = talkers[ talkerIndex ];
// We should only be checking this on the local user
assert( talker.IsLocal() );
bool ret = talker.hasHeadsetChanged;
talker.hasHeadsetChanged = false;
return ret;
}
+143
View File
@@ -0,0 +1,143 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __SYS_VOICECHATMGR_H__
#define __SYS_VOICECHATMGR_H__
#include "sys_lobby_backend.h"
/*
================================================
idVoiceChatMgr
================================================
*/
class idVoiceChatMgr {
public:
idVoiceChatMgr() : activeLobbyType( -1 ), activeGroupIndex( 0 ), sendFrame( 0 ), disableVoiceReasons( 0 ), sendGlobal( false ) {}
virtual void Init( void * pXAudio2 );
virtual void Shutdown();
void RegisterTalker( lobbyUser_t * user, int lobbyType, bool isLocal );
void UnregisterTalker( lobbyUser_t * user, int lobbyType, bool isLocal );
void GetActiveLocalTalkers( idStaticList< int, MAX_PLAYERS > & localTalkers );
void GetRecipientsForTalker( int talkerIndex, idStaticList< const lobbyAddress_t *, MAX_PLAYERS > & recipients );
void SetTalkerGroup( const lobbyUser_t * user, int lobbyType, int groupIndex );
void SetActiveLobby( int lobbyType );
void SetActiveChatGroup( int groupIndex );
int FindTalkerByUserId( lobbyUserID_t lobbyUserID, int lobbyType );
bool GetLocalChatData( int talkerIndex, byte * data, int & dataSize );
void SubmitIncomingChatData( const byte * data, int dataSize );
voiceState_t GetVoiceState( const lobbyUser_t * user );
bool CanSendVoiceTo( int talkerFromIndex, int talkerToIndex );
bool IsRestrictedByPrivleges();
void SetHeadsetState( int talkerIndex, bool state );
bool GetHeadsetState( int talkerIndex ) const { return talkers[ talkerIndex ].hasHeadset; }
bool HasHeadsetStateChanged( int talkerIndex );
enum disableVoiceReason_t {
REASON_GENERIC = BIT( 0 ),
REASON_PRIVILEGES = BIT( 1 ),
};
void SetDisableVoiceReason( disableVoiceReason_t reason );
void ClearDisableVoiceReason( disableVoiceReason_t reason );
virtual bool GetLocalChatDataInternal( int talkerIndex, byte * data, int & dataSize ) = 0;
virtual void SubmitIncomingChatDataInternal( int talkerIndex, const byte * data, int dataSize ) = 0;
virtual bool TalkerHasData( int talkerIndex ) = 0;
virtual void Pump() {}
virtual void FlushBuffers() {}
virtual void ToggleMuteLocal( const lobbyUser_t * src, const lobbyUser_t * target );
protected:
struct remoteMachine_t {
int lobbyType;
lobbyAddress_t address;
int refCount;
int sendFrame;
};
struct talker_t {
talker_t() :
user( NULL ),
isLocal( false ),
lobbyType( -1 ),
groupIndex( -1 ),
registered( false ),
registeredSuccess( false ),
machineIndex( -1 ),
isMuted( false ),
hasHeadset( true ),
hasHeadsetChanged( false ),
talking( false ),
talkingGlobal( false ),
talkingTime( 0 )
{}
lobbyUser_t * user;
bool isLocal;
int lobbyType;
int groupIndex;
bool registered; // True if this user is currently registered with the XHV engine
bool registeredSuccess; // True if this user is currently successfully registered with the XHV engine
int machineIndex; // Index into remote machines array (-1 if this is a local talker)
bool isMuted; // This machine is not allowed to hear or talk to this player
bool hasHeadset; // This user has a headset connected
bool hasHeadsetChanged; // This user's headset state has changed
bool talking;
bool talkingGlobal;
int talkingTime;
bool IsLocal() const { return isLocal; }
};
virtual bool RegisterTalkerInternal( int index ) = 0;
virtual void UnregisterTalkerInternal( int index ) = 0;
int FindTalkerIndex( const lobbyUser_t * user, int lobbyType );
int FindMachine( const lobbyAddress_t & address, int lobbyType );
int AddMachine( const lobbyAddress_t & address, int lobbyType );
void RemoveMachine( int machineIndex, int lobbyType );
void UpdateRegisteredTalkers();
idStaticList< talker_t, MAX_PLAYERS * 2 > talkers; // * 2 to account for handling both session types
idStaticList< remoteMachine_t, MAX_PLAYERS * 2 > remoteMachines; // * 2 to account for handling both session types
int activeLobbyType;
int activeGroupIndex;
int sendFrame;
uint32 disableVoiceReasons;
bool sendGlobal;
};
#endif // __SYS_VOICECHATMGR_H__
+109
View File
@@ -0,0 +1,109 @@
// Microsoft Visual C++ generated resource script.
//
#include "doom_resource.h"
#define APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 2 resource.
//
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
#undef APSTUDIO_READONLY_SYMBOLS
/////////////////////////////////////////////////////////////////////////////
// English (United States) resources
#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)
#ifdef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// TEXTINCLUDE
//
1 TEXTINCLUDE
BEGIN
"doom_resource.h\0"
END
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
END
2 TEXTINCLUDE
BEGIN
"#include ""afxres.h""\r\n"
"\0"
END
#endif // APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Icon
//
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
IDI_ICON1 ICON "res\\doom.ico"
/////////////////////////////////////////////////////////////////////////////
//
// Version
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,1
PRODUCTVERSION 1,0,0,1
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
#else
FILEFLAGS 0x0L
#endif
FILEOS 0x40004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "CompanyName", "id Software LLC, a ZeniMax Media company"
VALUE "FileDescription", "DOOM 3: BFG Edition"
VALUE "FileVersion", "1.0.0.1"
VALUE "InternalName", "Doom3BFG"
VALUE "LegalCopyright", "© 1993-2012 id Software LLC, a ZeniMax Media company"
VALUE "OriginalFilename", "Doom3BFG"
VALUE "ProductName", "DOOM® 3: BFG Edition™"
VALUE "ProductVersion", "1.0.0.1"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END
#endif // English (United States) resources
/////////////////////////////////////////////////////////////////////////////
#ifndef APSTUDIO_INVOKED
/////////////////////////////////////////////////////////////////////////////
//
// Generated from the TEXTINCLUDE 3 resource.
//
/////////////////////////////////////////////////////////////////////////////
#endif // not APSTUDIO_INVOKED
+18
View File
@@ -0,0 +1,18 @@
//{{NO_DEPENDENCIES}}
// Microsoft Visual C++ generated include file.
// Used by doom.rc
//
#define IDI_ICON1 4001
#define IDR_IDR_USYSGZPS11 4002
// Next default values for new objects
//
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_3D_CONTROLS 1
#define _APS_NEXT_RESOURCE_VALUE 4003
#define _APS_NEXT_COMMAND_VALUE 24000
#define _APS_NEXT_CONTROL_VALUE 4200
#define _APS_NEXT_SYMED_VALUE 4002
#endif
#endif
Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

+103
View File
@@ -0,0 +1,103 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
#include "win_achievements.h"
#include "../sys_session_local.h"
extern idCVar achievements_Verbose;
#define STEAM_ACHIEVEMENT_PREFIX "ach_"
/*
========================
idAchievementSystemWin::idAchievementSystemWin
========================
*/
idAchievementSystemWin::idAchievementSystemWin() {
}
/*
========================
idAchievementSystemWin::IsInitialized
========================
*/
bool idAchievementSystemWin::IsInitialized() {
return false;
}
/*
================================
idAchievementSystemWin::AchievementUnlock
================================
*/
void idAchievementSystemWin::AchievementUnlock( idLocalUser * user, int achievementID ) {
}
/*
========================
idAchievementSystemWin::AchievementLock
========================
*/
void idAchievementSystemWin::AchievementLock( idLocalUser * user, const int achievementID ) {
}
/*
========================
idAchievementSystemWin::AchievementLockAll
========================
*/
void idAchievementSystemWin::AchievementLockAll( idLocalUser * user, const int maxId ) {
}
/*
========================
idAchievementSystemWin::GetAchievementDescription
========================
*/
bool idAchievementSystemWin::GetAchievementDescription( idLocalUser * user, const int achievementID, achievementDescription_t & data ) const {
return false;
}
/*
========================
idAchievementSystemWin::GetAchievementState
========================
*/
bool idAchievementSystemWin::GetAchievementState( idLocalUser * user, idArray< bool, idAchievementSystem::MAX_ACHIEVEMENTS > & achievements ) const {
return false;
}
/*
================================
idAchievementSystemWin::Pump
================================
*/
void idAchievementSystemWin::Pump() {
}
+49
View File
@@ -0,0 +1,49 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __WIN_ACHIEVEMENTS_H__
#define __WIN_ACHIEVEMENTS_H__
/*
================================================
idAchievementSystemWin
================================================
*/
class idAchievementSystemWin : public idAchievementSystem {
public:
idAchievementSystemWin();
bool IsInitialized();
void AchievementUnlock( idLocalUser * user, const int achievementID );
void AchievementLock( idLocalUser * user, const int achievementID );
void AchievementLockAll( idLocalUser * user, const int maxId );
void Pump();
bool GetAchievementDescription( idLocalUser * user, const int id, achievementDescription_t & data ) const;
bool GetAchievementState( idLocalUser * user, idArray< bool, idAchievementSystem::MAX_ACHIEVEMENTS > & achievements ) const;
};
#endif // __WIN_ACHIEVEMENTS_H__
File diff suppressed because it is too large Load Diff
+93
View File
@@ -0,0 +1,93 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
/*
** WIN_GAMMA.C
*/
#include <assert.h>
#include "win_local.h"
#include "../../renderer/tr_local.h"
static unsigned short s_oldHardwareGamma[3][256];
/*
** WG_GetOldGammaRamp
**
*/
void WG_GetOldGammaRamp( void )
{
HDC hDC;
hDC = GetDC( GetDesktopWindow() );
GetDeviceGammaRamp( hDC, s_oldHardwareGamma );
ReleaseDC( GetDesktopWindow(), hDC );
/*
** GLimp_SetGamma
**
*/
void GLimp_SetGamma( unsigned char red[256], unsigned char green[256], unsigned char blue[256] )
{
unsigned short table[3][256];
int i;
if ( !glw_state.hDC )
{
return;
}
for ( i = 0; i < 256; i++ )
{
table[0][i] = ( ( ( unsigned short ) red[i] ) << 8 ) | red[i];
table[1][i] = ( ( ( unsigned short ) green[i] ) << 8 ) | green[i];
table[2][i] = ( ( ( unsigned short ) blue[i] ) << 8 ) | blue[i];
}
if ( !SetDeviceGammaRamp( glw_state.hDC, table ) ) {
common->Printf( "WARNING: SetDeviceGammaRamp failed.\n" );
}
}
/*
** WG_RestoreGamma
*/
void WG_RestoreGamma( void )
{
HDC hDC;
// if we never read in a reasonable looking
// table, don't write it out
if ( s_oldHardwareGamma[0][255] == 0 ) {
return;
}
hDC = GetDC( GetDesktopWindow() );
SetDeviceGammaRamp( hDC, s_oldHardwareGamma );
ReleaseDC( GetDesktopWindow(), hDC );
}
File diff suppressed because it is too large Load Diff
+967
View File
@@ -0,0 +1,967 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
#include "../sys_session_local.h"
#include "win_local.h"
#define DINPUT_BUFFERSIZE 256
/*
============================================================
DIRECT INPUT KEYBOARD CONTROL
============================================================
*/
bool IN_StartupKeyboard() {
HRESULT hr;
bool bExclusive;
bool bForeground;
bool bImmediate;
bool bDisableWindowsKey;
DWORD dwCoopFlags;
if (!win32.g_pdi) {
common->Printf("keyboard: DirectInput has not been started\n");
return false;
}
if (win32.g_pKeyboard) {
win32.g_pKeyboard->Release();
win32.g_pKeyboard = NULL;
}
// Detrimine where the buffer would like to be allocated
bExclusive = false;
bForeground = true;
bImmediate = false;
bDisableWindowsKey = true;
if( bExclusive )
dwCoopFlags = DISCL_EXCLUSIVE;
else
dwCoopFlags = DISCL_NONEXCLUSIVE;
if( bForeground )
dwCoopFlags |= DISCL_FOREGROUND;
else
dwCoopFlags |= DISCL_BACKGROUND;
// Disabling the windows key is only allowed only if we are in foreground nonexclusive
if( bDisableWindowsKey && !bExclusive && bForeground )
dwCoopFlags |= DISCL_NOWINKEY;
// Obtain an interface to the system keyboard device.
if( FAILED( hr = win32.g_pdi->CreateDevice( GUID_SysKeyboard, &win32.g_pKeyboard, NULL ) ) ) {
common->Printf("keyboard: couldn't find a keyboard device\n");
return false;
}
// Set the data format to "keyboard format" - a predefined data format
//
// A data format specifies which controls on a device we
// are interested in, and how they should be reported.
//
// This tells DirectInput that we will be passing an array
// of 256 bytes to IDirectInputDevice::GetDeviceState.
if( FAILED( hr = win32.g_pKeyboard->SetDataFormat( &c_dfDIKeyboard ) ) )
return false;
// Set the cooperativity level to let DirectInput know how
// this device should interact with the system and with other
// DirectInput applications.
hr = win32.g_pKeyboard->SetCooperativeLevel( win32.hWnd, dwCoopFlags );
if( hr == DIERR_UNSUPPORTED && !bForeground && bExclusive ) {
common->Printf("keyboard: SetCooperativeLevel() returned DIERR_UNSUPPORTED.\nFor security reasons, background exclusive keyboard access is not allowed.\n");
return false;
}
if( FAILED(hr) ) {
return false;
}
if( !bImmediate ) {
// IMPORTANT STEP TO USE BUFFERED DEVICE DATA!
//
// DirectInput uses unbuffered I/O (buffer size = 0) by default.
// If you want to read buffered data, you need to set a nonzero
// buffer size.
//
// Set the buffer size to DINPUT_BUFFERSIZE (defined above) elements.
//
// The buffer size is a DWORD property associated with the device.
DIPROPDWORD dipdw;
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
dipdw.diph.dwObj = 0;
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.dwData = DINPUT_BUFFERSIZE; // Arbitary buffer size
if( FAILED( hr = win32.g_pKeyboard->SetProperty( DIPROP_BUFFERSIZE, &dipdw.diph ) ) )
return false;
}
// Acquire the newly created device
win32.g_pKeyboard->Acquire();
common->Printf( "keyboard: DirectInput initialized.\n");
return true;
}
/*
==========================
IN_DeactivateKeyboard
==========================
*/
void IN_DeactivateKeyboard() {
if (!win32.g_pKeyboard) {
return;
}
win32.g_pKeyboard->Unacquire( );
}
/*
============================================================
DIRECT INPUT MOUSE CONTROL
============================================================
*/
/*
========================
IN_InitDirectInput
========================
*/
void IN_InitDirectInput() {
HRESULT hr;
common->Printf( "Initializing DirectInput...\n" );
if ( win32.g_pdi != NULL ) {
win32.g_pdi->Release(); // if the previous window was destroyed we need to do this
win32.g_pdi = NULL;
}
// Register with the DirectInput subsystem and get a pointer
// to a IDirectInput interface we can use.
// Create the base DirectInput object
if ( FAILED( hr = DirectInput8Create( GetModuleHandle(NULL), DIRECTINPUT_VERSION, IID_IDirectInput8, (void**)&win32.g_pdi, NULL ) ) ) {
common->Printf ("DirectInputCreate failed\n");
}
}
/*
========================
IN_InitDIMouse
========================
*/
bool IN_InitDIMouse() {
HRESULT hr;
if ( win32.g_pdi == NULL) {
return false;
}
// obtain an interface to the system mouse device.
hr = win32.g_pdi->CreateDevice( GUID_SysMouse, &win32.g_pMouse, NULL);
if (FAILED(hr)) {
common->Printf ("mouse: Couldn't open DI mouse device\n");
return false;
}
// Set the data format to "mouse format" - a predefined data format
//
// A data format specifies which controls on a device we
// are interested in, and how they should be reported.
//
// This tells DirectInput that we will be passing a
// DIMOUSESTATE2 structure to IDirectInputDevice::GetDeviceState.
if( FAILED( hr = win32.g_pMouse->SetDataFormat( &c_dfDIMouse2 ) ) ) {
common->Printf ("mouse: Couldn't set DI mouse format\n");
return false;
}
// set the cooperativity level.
hr = win32.g_pMouse->SetCooperativeLevel( win32.hWnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
if (FAILED(hr)) {
common->Printf ("mouse: Couldn't set DI coop level\n");
return false;
}
// IMPORTANT STEP TO USE BUFFERED DEVICE DATA!
//
// DirectInput uses unbuffered I/O (buffer size = 0) by default.
// If you want to read buffered data, you need to set a nonzero
// buffer size.
//
// Set the buffer size to SAMPLE_BUFFER_SIZE (defined above) elements.
//
// The buffer size is a DWORD property associated with the device.
DIPROPDWORD dipdw;
dipdw.diph.dwSize = sizeof(DIPROPDWORD);
dipdw.diph.dwHeaderSize = sizeof(DIPROPHEADER);
dipdw.diph.dwObj = 0;
dipdw.diph.dwHow = DIPH_DEVICE;
dipdw.dwData = DINPUT_BUFFERSIZE; // Arbitary buffer size
if( FAILED( hr = win32.g_pMouse->SetProperty( DIPROP_BUFFERSIZE, &dipdw.diph ) ) ) {
common->Printf ("mouse: Couldn't set DI buffersize\n");
return false;
}
IN_ActivateMouse();
// clear any pending samples
int mouseEvents[MAX_MOUSE_EVENTS][2];
Sys_PollMouseInputEvents( mouseEvents );
common->Printf( "mouse: DirectInput initialized.\n");
return true;
}
/*
==========================
IN_ActivateMouse
==========================
*/
void IN_ActivateMouse() {
int i;
HRESULT hr;
if ( !win32.in_mouse.GetBool() || win32.mouseGrabbed || !win32.g_pMouse ) {
return;
}
win32.mouseGrabbed = true;
for ( i = 0; i < 10; i++ ) {
if ( ::ShowCursor( false ) < 0 ) {
break;
}
}
// we may fail to reacquire if the window has been recreated
hr = win32.g_pMouse->Acquire();
if (FAILED(hr)) {
return;
}
// set the cooperativity level.
hr = win32.g_pMouse->SetCooperativeLevel( win32.hWnd, DISCL_EXCLUSIVE | DISCL_FOREGROUND);
}
/*
==========================
IN_DeactivateMouse
==========================
*/
void IN_DeactivateMouse() {
int i;
if (!win32.g_pMouse || !win32.mouseGrabbed ) {
return;
}
win32.g_pMouse->Unacquire();
for ( i = 0; i < 10; i++ ) {
if ( ::ShowCursor( true ) >= 0 ) {
break;
}
}
win32.mouseGrabbed = false;
}
/*
==========================
IN_DeactivateMouseIfWindowed
==========================
*/
void IN_DeactivateMouseIfWindowed() {
if ( !win32.cdsFullscreen ) {
IN_DeactivateMouse();
}
}
/*
============================================================
MOUSE CONTROL
============================================================
*/
/*
===========
Sys_ShutdownInput
===========
*/
void Sys_ShutdownInput() {
IN_DeactivateMouse();
IN_DeactivateKeyboard();
if ( win32.g_pKeyboard ) {
win32.g_pKeyboard->Release();
win32.g_pKeyboard = NULL;
}
if ( win32.g_pMouse ) {
win32.g_pMouse->Release();
win32.g_pMouse = NULL;
}
if ( win32.g_pdi ) {
win32.g_pdi->Release();
win32.g_pdi = NULL;
}
}
/*
===========
Sys_InitInput
===========
*/
void Sys_InitInput() {
common->Printf ("\n------- Input Initialization -------\n");
IN_InitDirectInput();
if ( win32.in_mouse.GetBool() ) {
IN_InitDIMouse();
// don't grab the mouse on initialization
Sys_GrabMouseCursor( false );
} else {
common->Printf ("Mouse control not active.\n");
}
IN_StartupKeyboard();
common->Printf ("------------------------------------\n");
win32.in_mouse.ClearModified();
}
/*
==================
IN_Frame
Called every frame, even if not generating commands
==================
*/
void IN_Frame() {
bool shouldGrab = true;
if ( !win32.in_mouse.GetBool() ) {
shouldGrab = false;
}
// if fullscreen, we always want the mouse
if ( !win32.cdsFullscreen ) {
if ( win32.mouseReleased ) {
shouldGrab = false;
}
if ( win32.movingWindow ) {
shouldGrab = false;
}
if ( !win32.activeApp ) {
shouldGrab = false;
}
}
if ( shouldGrab != win32.mouseGrabbed ) {
if ( usercmdGen != NULL ) {
usercmdGen->Clear();
}
if ( win32.mouseGrabbed ) {
IN_DeactivateMouse();
} else {
IN_ActivateMouse();
#if 0 // if we can't reacquire, try reinitializing
if ( !IN_InitDIMouse() ) {
win32.in_mouse.SetBool( false );
return;
}
#endif
}
}
}
void Sys_GrabMouseCursor( bool grabIt ) {
win32.mouseReleased = !grabIt;
if ( !grabIt ) {
// release it right now
IN_Frame();
}
}
//=====================================================================================
static DIDEVICEOBJECTDATA polled_didod[ DINPUT_BUFFERSIZE ]; // Receives buffered data
static int diFetch;
static byte toggleFetch[2][ 256 ];
#if 1
// I tried doing the full-state get to address a keyboard problem on one system,
// but it didn't make any difference
/*
====================
Sys_PollKeyboardInputEvents
====================
*/
int Sys_PollKeyboardInputEvents() {
DWORD dwElements;
HRESULT hr;
if( win32.g_pKeyboard == NULL ) {
return 0;
}
dwElements = DINPUT_BUFFERSIZE;
hr = win32.g_pKeyboard->GetDeviceData( sizeof(DIDEVICEOBJECTDATA),
polled_didod, &dwElements, 0 );
if( hr != DI_OK )
{
// We got an error or we got DI_BUFFEROVERFLOW.
//
// Either way, it means that continuous contact with the
// device has been lost, either due to an external
// interruption, or because the buffer overflowed
// and some events were lost.
hr = win32.g_pKeyboard->Acquire();
// nuke the garbage
if (!FAILED(hr)) {
//Bug 951: The following command really clears the garbage input.
//The original will still process keys in the buffer and was causing
//some problems.
win32.g_pKeyboard->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), NULL, &dwElements, 0 );
dwElements = 0;
}
// hr may be DIERR_OTHERAPPHASPRIO or other errors. This
// may occur when the app is minimized or in the process of
// switching, so just try again later
}
if( FAILED(hr) ) {
return 0;
}
return dwElements;
}
#else
/*
====================
Sys_PollKeyboardInputEvents
Fake events by getting the entire device state
and checking transitions
====================
*/
int Sys_PollKeyboardInputEvents() {
HRESULT hr;
if( win32.g_pKeyboard == NULL ) {
return 0;
}
hr = win32.g_pKeyboard->GetDeviceState( sizeof( toggleFetch[ diFetch ] ), toggleFetch[ diFetch ] );
if( hr != DI_OK )
{
// We got an error or we got DI_BUFFEROVERFLOW.
//
// Either way, it means that continuous contact with the
// device has been lost, either due to an external
// interruption, or because the buffer overflowed
// and some events were lost.
hr = win32.g_pKeyboard->Acquire();
// nuke the garbage
if (!FAILED(hr)) {
hr = win32.g_pKeyboard->GetDeviceState( sizeof( toggleFetch[ diFetch ] ), toggleFetch[ diFetch ] );
}
// hr may be DIERR_OTHERAPPHASPRIO or other errors. This
// may occur when the app is minimized or in the process of
// switching, so just try again later
}
if( FAILED(hr) ) {
return 0;
}
// build faked events
int numChanges = 0;
for ( int i = 0 ; i < 256 ; i++ ) {
if ( toggleFetch[0][i] != toggleFetch[1][i] ) {
polled_didod[ numChanges ].dwOfs = i;
polled_didod[ numChanges ].dwData = toggleFetch[ diFetch ][i] ? 0x80 : 0;
numChanges++;
}
}
diFetch ^= 1;
return numChanges;
}
#endif
/*
====================
Sys_PollKeyboardInputEvents
====================
*/
int Sys_ReturnKeyboardInputEvent( const int n, int &ch, bool &state ) {
ch = polled_didod[ n ].dwOfs;
state = ( polled_didod[ n ].dwData & 0x80 ) == 0x80;
if ( ch == K_PRINTSCREEN || ch == K_LCTRL || ch == K_LALT || ch == K_RCTRL || ch == K_RALT ) {
// for windows, add a keydown event for print screen here, since
// windows doesn't send keydown events to the WndProc for this key.
// ctrl and alt are handled here to get around windows sending ctrl and
// alt messages when the right-alt is pressed on non-US 102 keyboards.
Sys_QueEvent( SE_KEY, ch, state, 0, NULL, 0 );
}
return ch;
}
void Sys_EndKeyboardInputEvents() {
}
//=====================================================================================
int Sys_PollMouseInputEvents( int mouseEvents[MAX_MOUSE_EVENTS][2] ) {
DWORD dwElements;
HRESULT hr;
if ( !win32.g_pMouse || !win32.mouseGrabbed ) {
return 0;
}
dwElements = DINPUT_BUFFERSIZE;
hr = win32.g_pMouse->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), polled_didod, &dwElements, 0 );
if( hr != DI_OK ) {
hr = win32.g_pMouse->Acquire();
// clear the garbage
if (!FAILED(hr)) {
win32.g_pMouse->GetDeviceData( sizeof(DIDEVICEOBJECTDATA), polled_didod, &dwElements, 0 );
}
}
if( FAILED(hr) ) {
return 0;
}
if ( dwElements > MAX_MOUSE_EVENTS ) {
dwElements = MAX_MOUSE_EVENTS;
}
for( DWORD i = 0; i < dwElements; i++ ) {
mouseEvents[i][0] = M_INVALID;
mouseEvents[i][1] = 0;
if ( polled_didod[i].dwOfs >= DIMOFS_BUTTON0 && polled_didod[i].dwOfs <= DIMOFS_BUTTON7 ) {
const int mouseButton = ( polled_didod[i].dwOfs - DIMOFS_BUTTON0 );
const bool mouseDown = (polled_didod[i].dwData & 0x80) == 0x80;
mouseEvents[i][0] = M_ACTION1 + mouseButton;
mouseEvents[i][1] = mouseDown;
Sys_QueEvent( SE_KEY, K_MOUSE1 + mouseButton, mouseDown, 0, NULL, 0 );
} else {
switch (polled_didod[i].dwOfs) {
case DIMOFS_X:
mouseEvents[i][0] = M_DELTAX;
mouseEvents[i][1] = polled_didod[i].dwData;
Sys_QueEvent( SE_MOUSE, polled_didod[i].dwData, 0, 0, NULL, 0 );
break;
case DIMOFS_Y:
mouseEvents[i][0] = M_DELTAY;
mouseEvents[i][1] = polled_didod[i].dwData;
Sys_QueEvent( SE_MOUSE, 0, polled_didod[i].dwData, 0, NULL, 0 );
break;
case DIMOFS_Z:
mouseEvents[i][0] = M_DELTAZ;
mouseEvents[i][1] = (int)polled_didod[i].dwData / WHEEL_DELTA;
{
const int value = (int)polled_didod[i].dwData / WHEEL_DELTA;
const int key = value < 0 ? K_MWHEELDOWN : K_MWHEELUP;
const int iterations = abs( value );
for ( int i = 0; i < iterations; i++ ) {
Sys_QueEvent( SE_KEY, key, true, 0, NULL, 0 );
Sys_QueEvent( SE_KEY, key, false, 0, NULL, 0 );
}
}
break;
}
}
}
return dwElements;
}
//=====================================================================================
// Joystick Input Handling
//=====================================================================================
void Sys_SetRumble( int device, int low, int hi ) {
return win32.g_Joystick.SetRumble( device, low, hi );
}
int Sys_PollJoystickInputEvents( int deviceNum ) {
return win32.g_Joystick.PollInputEvents( deviceNum );
}
int Sys_ReturnJoystickInputEvent( const int n, int &action, int &value ) {
return win32.g_Joystick.ReturnInputEvent( n, action, value );
}
void Sys_EndJoystickInputEvents() {
}
/*
========================
JoystickSamplingThread
========================
*/
static int threadTimeDeltas[256];
static int threadPacket[256];
static int threadCount;
void JoystickSamplingThread( void *data ) {
static int prevTime = 0;
static uint64 nextCheck[MAX_JOYSTICKS] = { 0 };
const uint64 waitTime = 5000000; // poll every 5 seconds to see if a controller was connected
while( 1 ) {
// hopefully we see close to 4000 usec each loop
int now = Sys_Microseconds();
int delta;
if ( prevTime == 0 ) {
delta = 4000;
} else {
delta = now - prevTime;
}
prevTime = now;
threadTimeDeltas[threadCount&255] = delta;
threadCount++;
{
XINPUT_STATE joyData[MAX_JOYSTICKS];
bool validData[MAX_JOYSTICKS];
for ( int i = 0 ; i < MAX_JOYSTICKS ; i++ ) {
if ( now >= nextCheck[i] ) {
// XInputGetState might block... for a _really_ long time..
validData[i] = XInputGetState( i, &joyData[i] ) == ERROR_SUCCESS;
// allow an immediate data poll if the input device is connected else
// wait for some time to see if another device was reconnected.
// Checking input state infrequently for newly connected devices prevents
// severe slowdowns on PC, especially on WinXP64.
if ( validData[i] ) {
nextCheck[i] = 0;
} else {
nextCheck[i] = now + waitTime;
}
}
}
// do this short amount of processing inside a critical section
idScopedCriticalSection cs( win32.g_Joystick.mutexXis );
for ( int i = 0 ; i < MAX_JOYSTICKS ; i++ ) {
controllerState_t * cs = &win32.g_Joystick.controllers[i];
if ( !validData[i] ) {
cs->valid = false;
continue;
}
cs->valid = true;
XINPUT_STATE& current = joyData[i];
cs->current = current;
// Switch from using cs->current to current to reduce chance of Load-Hit-Store on consoles
threadPacket[threadCount&255] = current.dwPacketNumber;
#if 0
if ( xis.dwPacketNumber == oldXis[ inputDeviceNum ].dwPacketNumber ) {
return numEvents;
}
#endif
cs->buttonBits |= current.Gamepad.wButtons;
}
}
// we want this to be processed at least 250 times a second
WaitForSingleObject( win32.g_Joystick.timer, INFINITE );
}
}
/*
========================
idJoystickWin32::idJoystickWin32
========================
*/
idJoystickWin32::idJoystickWin32() {
numEvents = 0;
memset( &events, 0, sizeof( events ) );
memset( &controllers, 0, sizeof( controllers ) );
memset( buttonStates, 0, sizeof( buttonStates ) );
memset( joyAxis, 0, sizeof( joyAxis ) );
}
/*
========================
idJoystickWin32::Init
========================
*/
bool idJoystickWin32::Init() {
idJoystick::Init();
// setup the timer that the high frequency thread will wait on
// to fire every 4 msec
timer = CreateWaitableTimer( NULL, FALSE, "JoypadTimer" );
LARGE_INTEGER dueTime;
dueTime.QuadPart = -1;
if ( !SetWaitableTimer( timer, &dueTime, 4, NULL, NULL, FALSE ) ) {
idLib::FatalError( "SetWaitableTimer for joystick failed" );
}
// spawn the high frequency joystick reading thread
Sys_CreateThread( (xthread_t)JoystickSamplingThread, NULL, THREAD_HIGHEST, "Joystick", CORE_1A );
return false;
}
/*
========================
idJoystickWin32::SetRumble
========================
*/
void idJoystickWin32::SetRumble( int inputDeviceNum, int rumbleLow, int rumbleHigh ) {
if ( inputDeviceNum < 0 || inputDeviceNum >= MAX_JOYSTICKS ) {
return;
}
if ( !controllers[inputDeviceNum].valid ) {
return;
}
XINPUT_VIBRATION vibration;
vibration.wLeftMotorSpeed = idMath::ClampInt( 0, 65535, rumbleLow );
vibration.wRightMotorSpeed = idMath::ClampInt( 0, 65535, rumbleHigh );
DWORD err = XInputSetState( inputDeviceNum, &vibration );
if ( err != ERROR_SUCCESS ) {
idLib::Warning( "XInputSetState error: 0x%x", err );
}
}
/*
========================
idJoystickWin32::PostInputEvent
========================
*/
void idJoystickWin32::PostInputEvent( int inputDeviceNum, int event, int value, int range ) {
// These events are used for GUI button presses
if ( ( event >= J_ACTION1 ) && ( event <= J_ACTION_MAX ) ) {
PushButton( inputDeviceNum, K_JOY1 + ( event - J_ACTION1 ), value != 0 );
} else if ( event == J_AXIS_LEFT_X ) {
PushButton( inputDeviceNum, K_JOY_STICK1_LEFT, ( value < -range ) );
PushButton( inputDeviceNum, K_JOY_STICK1_RIGHT, ( value > range ) );
} else if ( event == J_AXIS_LEFT_Y ) {
PushButton( inputDeviceNum, K_JOY_STICK1_UP, ( value < -range ) );
PushButton( inputDeviceNum, K_JOY_STICK1_DOWN, ( value > range ) );
} else if ( event == J_AXIS_RIGHT_X ) {
PushButton( inputDeviceNum, K_JOY_STICK2_LEFT, ( value < -range ) );
PushButton( inputDeviceNum, K_JOY_STICK2_RIGHT, ( value > range ) );
} else if ( event == J_AXIS_RIGHT_Y ) {
PushButton( inputDeviceNum, K_JOY_STICK2_UP, ( value < -range ) );
PushButton( inputDeviceNum, K_JOY_STICK2_DOWN, ( value > range ) );
} else if ( ( event >= J_DPAD_UP ) && ( event <= J_DPAD_RIGHT ) ) {
PushButton( inputDeviceNum, K_JOY_DPAD_UP + ( event - J_DPAD_UP ), value != 0 );
} else if ( event == J_AXIS_LEFT_TRIG ) {
PushButton( inputDeviceNum, K_JOY_TRIGGER1, ( value > range ) );
} else if ( event == J_AXIS_RIGHT_TRIG ) {
PushButton( inputDeviceNum, K_JOY_TRIGGER2, ( value > range ) );
}
if ( event >= J_AXIS_MIN && event <= J_AXIS_MAX ) {
int axis = event - J_AXIS_MIN;
int percent = ( value * 16 ) / range;
if ( joyAxis[inputDeviceNum][axis] != percent ) {
joyAxis[inputDeviceNum][axis] = percent;
Sys_QueEvent( SE_JOYSTICK, axis, percent, 0, NULL, inputDeviceNum );
}
}
// These events are used for actual game input
events[numEvents].event = event;
events[numEvents].value = value;
numEvents++;
}
/*
========================
idJoystickWin32::PollInputEvents
========================
*/
int idJoystickWin32::PollInputEvents( int inputDeviceNum ) {
numEvents = 0;
if ( !win32.activeApp ) {
return numEvents;
}
assert( inputDeviceNum < 4 );
// if ( inputDeviceNum > in_joystick.GetInteger() ) {
// return numEvents;
// }
controllerState_t *cs = &controllers[ inputDeviceNum ];
// grab the current packet under a critical section
XINPUT_STATE xis;
XINPUT_STATE old;
int orBits;
{
idScopedCriticalSection crit( mutexXis );
xis = cs->current;
old = cs->previous;
cs->previous = xis;
// fetch or'd button bits
orBits = cs->buttonBits;
cs->buttonBits = 0;
}
#if 0
if ( XInputGetState( inputDeviceNum, &xis ) != ERROR_SUCCESS ) {
return numEvents;
}
#endif
for ( int i = 0 ; i < 32 ; i++ ) {
int bit = 1<<i;
if ( ( ( xis.Gamepad.wButtons | old.Gamepad.wButtons ) & bit ) == 0
&& ( orBits & bit ) ) {
idLib::Printf( "Dropped button press on bit %i\n", i );
}
}
if ( session->IsSystemUIShowing() ) {
// memset xis so the current input does not get latched if the UI is showing
memset( &xis, 0, sizeof( XINPUT_STATE ) );
}
int joyRemap[16] = {
J_DPAD_UP, J_DPAD_DOWN, // Up, Down
J_DPAD_LEFT, J_DPAD_RIGHT, // Left, Right
J_ACTION9, J_ACTION10, // Start, Back
J_ACTION7, J_ACTION8, // Left Stick Down, Right Stick Down
J_ACTION5, J_ACTION6, // Black, White (Left Shoulder, Right Shoulder)
0, 0, // Unused
J_ACTION1, J_ACTION2, // A, B
J_ACTION3, J_ACTION4, // X, Y
};
// Check the digital buttons
for ( int i = 0; i < 16; i++ ) {
int mask = ( 1 << i );
if ( ( xis.Gamepad.wButtons & mask ) != ( old.Gamepad.wButtons & mask ) ) {
PostInputEvent( inputDeviceNum, joyRemap[i], ( xis.Gamepad.wButtons & mask ) > 0 );
}
}
// Check the triggers
if ( xis.Gamepad.bLeftTrigger != old.Gamepad.bLeftTrigger ) {
PostInputEvent( inputDeviceNum, J_AXIS_LEFT_TRIG, xis.Gamepad.bLeftTrigger * 128 );
}
if ( xis.Gamepad.bRightTrigger != old.Gamepad.bRightTrigger ) {
PostInputEvent( inputDeviceNum, J_AXIS_RIGHT_TRIG, xis.Gamepad.bRightTrigger * 128 );
}
if ( xis.Gamepad.sThumbLX != old.Gamepad.sThumbLX ) {
PostInputEvent( inputDeviceNum, J_AXIS_LEFT_X, xis.Gamepad.sThumbLX );
}
if ( xis.Gamepad.sThumbLY != old.Gamepad.sThumbLY ) {
PostInputEvent( inputDeviceNum, J_AXIS_LEFT_Y, -xis.Gamepad.sThumbLY );
}
if ( xis.Gamepad.sThumbRX != old.Gamepad.sThumbRX ) {
PostInputEvent( inputDeviceNum, J_AXIS_RIGHT_X, xis.Gamepad.sThumbRX );
}
if ( xis.Gamepad.sThumbRY != old.Gamepad.sThumbRY ) {
PostInputEvent( inputDeviceNum, J_AXIS_RIGHT_Y, -xis.Gamepad.sThumbRY );
}
return numEvents;
}
/*
========================
idJoystickWin32::ReturnInputEvent
========================
*/
int idJoystickWin32::ReturnInputEvent( const int n, int & action, int &value ) {
if ( ( n < 0 ) || ( n >= MAX_JOY_EVENT ) ) {
return 0;
}
action = events[ n ].event;
value = events[ n ].value;
return 1;
}
/*
========================
idJoystickWin32::PushButton
========================
*/
void idJoystickWin32::PushButton( int inputDeviceNum, int key, bool value ) {
// So we don't keep sending the same SE_KEY message over and over again
if ( buttonStates[inputDeviceNum][key] != value ) {
buttonStates[inputDeviceNum][key] = value;
Sys_QueEvent( SE_KEY, key, value, 0, NULL, inputDeviceNum );
}
}
+96
View File
@@ -0,0 +1,96 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
//#if defined( ID_VS2010 )
//#include "../../../libs/dxsdk_June2010/include/xinput.h"
//#else
#include <Xinput.h>
//#endif
static const int MAX_JOYSTICKS = 4;
/*
================================================================================================
Joystick Win32
================================================================================================
*/
struct controllerState_t {
// the current states are updated by the input thread at 250 hz
XINPUT_STATE current;
// the previous state is latched at polling time
XINPUT_STATE previous;
// The current button bits are or'd into this at the high sampling rate, then
// zero'd by the main thread when a usercmd_t is created. This prevents the
// complete missing of a button press that went down and up between two usercmd_t
// creations, although it can add sn extra frame of latency to sensing a release.
int buttonBits;
// Only valid controllers will have their rumble set
bool valid;
};
class idJoystickWin32 : idJoystick {
public:
idJoystickWin32();
virtual bool Init();
virtual void SetRumble( int deviceNum, int rumbleLow, int rumbleHigh );
virtual int PollInputEvents( int inputDeviceNum );
virtual int ReturnInputEvent( const int n, int &action, int &value );
virtual void EndInputEvents() {}
protected:
friend void JoystickSamplingThread( void *data );
void PushButton( int inputDeviceNum, int key, bool value );
void PostInputEvent( int inputDeviceNum, int event, int value, int range = 16384 );
idSysMutex mutexXis; // lock this before using currentXis or stickIntegrations
HANDLE timer; // fire every 4 msec
int numEvents;
struct {
int event;
int value;
} events[ MAX_JOY_EVENT ];
controllerState_t controllers[ MAX_JOYSTICKS ];
// should these be per-controller?
bool buttonStates[MAX_INPUT_DEVICES][K_LAST_KEY]; // For keeping track of button up/down events
int joyAxis[MAX_INPUT_DEVICES][MAX_JOYSTICK_AXIS]; // For keeping track of joystick axises
};
+166
View File
@@ -0,0 +1,166 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __WIN_LOCAL_H__
#define __WIN_LOCAL_H__
#include <windows.h>
#include "../../renderer/OpenGL/wglext.h" // windows OpenGL extensions
#include "win_input.h"
// WGL_ARB_extensions_string
extern PFNWGLGETEXTENSIONSSTRINGARBPROC wglGetExtensionsStringARB;
// WGL_EXT_swap_interval
extern PFNWGLSWAPINTERVALEXTPROC wglSwapIntervalEXT;
// WGL_ARB_pixel_format
extern PFNWGLGETPIXELFORMATATTRIBIVARBPROC wglGetPixelFormatAttribivARB;
extern PFNWGLGETPIXELFORMATATTRIBFVARBPROC wglGetPixelFormatAttribfvARB;
extern PFNWGLCHOOSEPIXELFORMATARBPROC wglChoosePixelFormatARB;
// WGL_ARB_pbuffer
extern PFNWGLCREATEPBUFFERARBPROC wglCreatePbufferARB;
extern PFNWGLGETPBUFFERDCARBPROC wglGetPbufferDCARB;
extern PFNWGLRELEASEPBUFFERDCARBPROC wglReleasePbufferDCARB;
extern PFNWGLDESTROYPBUFFERARBPROC wglDestroyPbufferARB;
extern PFNWGLQUERYPBUFFERARBPROC wglQueryPbufferARB;
// WGL_ARB_render_texture
extern PFNWGLBINDTEXIMAGEARBPROC wglBindTexImageARB;
extern PFNWGLRELEASETEXIMAGEARBPROC wglReleaseTexImageARB;
extern PFNWGLSETPBUFFERATTRIBARBPROC wglSetPbufferAttribARB;
#define WINDOW_STYLE (WS_OVERLAPPED|WS_BORDER|WS_CAPTION|WS_VISIBLE | WS_THICKFRAME)
void Sys_QueEvent( sysEventType_t type, int value, int value2, int ptrLength, void *ptr, int inputDeviceNum );
void Sys_CreateConsole();
void Sys_DestroyConsole();
char *Sys_ConsoleInput ();
char *Sys_GetCurrentUser();
void Win_SetErrorText( const char *text );
cpuid_t Sys_GetCPUId();
// Input subsystem
void IN_Init ();
void IN_Shutdown ();
// add additional non keyboard / non mouse movement on top of the keyboard move cmd
void IN_DeactivateMouseIfWindowed();
void IN_DeactivateMouse();
void IN_ActivateMouse();
void IN_Frame();
void DisableTaskKeys( BOOL bDisable, BOOL bBeep, BOOL bTaskMgr );
uint64 Sys_Microseconds();
// window procedure
LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
void Conbuf_AppendText( const char *msg );
typedef struct {
HWND hWnd;
HINSTANCE hInstance;
bool activeApp; // changed with WM_ACTIVATE messages
bool mouseReleased; // when the game has the console down or is doing a long operation
bool movingWindow; // inhibit mouse grab when dragging the window
bool mouseGrabbed; // current state of grab and hide
OSVERSIONINFOEX osversion;
cpuid_t cpuid;
// when we get a windows message, we store the time off so keyboard processing
// can know the exact time of an event (not really needed now that we use async direct input)
int sysMsgTime;
bool windowClassRegistered;
WNDPROC wndproc;
HDC hDC; // handle to device context
HGLRC hGLRC; // handle to GL rendering context
PIXELFORMATDESCRIPTOR pfd;
int pixelformat;
HINSTANCE hinstOpenGL; // HINSTANCE for the OpenGL library
int desktopBitsPixel;
int desktopWidth, desktopHeight;
int cdsFullscreen; // 0 = not fullscreen, otherwise monitor number
idFileHandle log_fp;
unsigned short oldHardwareGamma[3][256];
// desktop gamma is saved here for restoration at exit
static idCVar sys_arch;
static idCVar sys_cpustring;
static idCVar in_mouse;
static idCVar win_allowAltTab;
static idCVar win_notaskkeys;
static idCVar win_username;
static idCVar win_outputEditString;
static idCVar win_viewlog;
static idCVar win_timerUpdate;
static idCVar win_allowMultipleInstances;
CRITICAL_SECTION criticalSections[MAX_CRITICAL_SECTIONS];
HINSTANCE hInstDI; // direct input
LPDIRECTINPUT8 g_pdi;
LPDIRECTINPUTDEVICE8 g_pMouse;
LPDIRECTINPUTDEVICE8 g_pKeyboard;
idJoystickWin32 g_Joystick;
HANDLE renderCommandsEvent;
HANDLE renderCompletedEvent;
HANDLE renderActiveEvent;
HANDLE renderThreadHandle;
unsigned long renderThreadId;
void (*glimpRenderThread)();
void *smpData;
int wglErrors;
// SMP acceleration vars
} Win32Vars_t;
extern Win32Vars_t win32;
#endif /* !__WIN_LOCAL_H__ */
+121
View File
@@ -0,0 +1,121 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
#include "win_localuser.h"
extern idCVar win_userPersistent;
extern idCVar win_userOnline;
extern idCVar win_isInParty;
extern idCVar win_partyCount;
/*
========================
idLocalUserWin::Init
========================
*/
void idLocalUserWin::Init( int inputDevice_, const char * gamertag_, int numLocalUsers ) {
if ( numLocalUsers == 1 ) { // Check for 1, since this is now incremented before we get in here
// This is the master user
gamertag = gamertag_;
} else {
// On steam, we need to generate a name based off the master user for split-screen users.
// We use the number of users on the system to generate the name rather than the device
// number so that it is always consistently "username (2)" for the second player.
gamertag.Format( "%s (%i)", gamertag_, numLocalUsers );
}
inputDevice = inputDevice_;
}
/*
========================
idLocalUserWin::IsProfileReady
========================
*/
bool idLocalUserWin::IsProfileReady() const {
#ifdef _DEBUG
return win_userPersistent.GetBool();
#else
return true;
#endif
}
/*
========================
idLocalUserWin::IsOnline
========================
*/
bool idLocalUserWin::IsOnline() const {
#ifdef _DEBUG
return win_userOnline.GetBool();
#else
return true;
#endif
}
/*
========================
idLocalUserWin::IsInParty
========================
*/
bool idLocalUserWin::IsInParty() const {
#ifdef _DEBUG
return win_isInParty.GetBool();
#else
return false;
#endif
}
/*
========================
idLocalUserWin::GetPartyCount
========================
*/
int idLocalUserWin::GetPartyCount() const {
// TODO: Implement
#ifdef _DEBUG
return win_partyCount.GetInteger();
#else
return 0;
#endif
}
/*
========================
idLocalUserWin::VerifyUserState
========================
*/
bool idLocalUserWin::VerifyUserState( winUserState_t & state ) {
if ( state.inputDevice != inputDevice ) {
return false;
}
return true;
}
+75
View File
@@ -0,0 +1,75 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __WIN_LOCALUSER_H__
#define __WIN_LOCALUSER_H__
// This is to quickly get/set the data needed for disc-swapping
typedef struct {
int inputDevice;
} winUserState_t;
/*
================================================
idLocalUserWin
================================================
*/
class idLocalUserWin : public idLocalUser {
public:
static const int MAX_GAMERTAG = 64; // max number of bytes for a gamertag
static const int MAX_GAMERTAG_CHARS = 16; // max number of UTF-8 characters to show
idLocalUserWin() : inputDevice( 0 ) {}
//==========================================================================================
// idLocalUser interface
//==========================================================================================
virtual bool IsProfileReady() const;
virtual bool IsOnline() const;
virtual bool IsInParty() const;
virtual int GetPartyCount() const;
virtual uint32 GetOnlineCaps() const { return ( IsPersistent() && IsOnline() ) ? ( CAP_IS_ONLINE | CAP_CAN_PLAY_ONLINE ) : 0; }
virtual int GetInputDevice() const { return inputDevice; }
virtual const char * GetGamerTag() const { return gamertag.c_str(); }
virtual void PumpPlatform() {}
//==========================================================================================
// idLocalUserWin interface
//==========================================================================================
void SetInputDevice( int inputDevice_ ) { inputDevice = inputDevice_; }
void SetGamerTag( const char * gamerTag_ ) { gamertag = gamerTag_; }
winUserState_t GetUserState() { winUserState_t a = { inputDevice }; return a; }
bool VerifyUserState( winUserState_t & state );
void Init( int inputDevice_, const char * gamertag_, int numLocalUsers );
private:
idStrStatic< MAX_GAMERTAG > gamertag;
int inputDevice;
};
#endif // __WIN_LOCALUSER_H__
File diff suppressed because it is too large Load Diff
+987
View File
@@ -0,0 +1,987 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
/*
================================================================================================
Contains the NetworkSystem implementation specific to Win32.
================================================================================================
*/
#include <iptypes.h>
#include <iphlpapi.h>
static WSADATA winsockdata;
static bool winsockInitialized = false;
static bool usingSocks = false;
//lint -e569 ioctl macros trigger this
// force these libs to be included, so users of idLib don't need to add them to every project
#pragma comment(lib, "iphlpapi.lib" )
#pragma comment(lib, "wsock32.lib" )
/*
================================================================================================
Network CVars
================================================================================================
*/
idCVar net_socksServer( "net_socksServer", "", CVAR_ARCHIVE, "" );
idCVar net_socksPort( "net_socksPort", "1080", CVAR_ARCHIVE | CVAR_INTEGER, "" );
idCVar net_socksUsername( "net_socksUsername", "", CVAR_ARCHIVE, "" );
idCVar net_socksPassword( "net_socksPassword", "", CVAR_ARCHIVE, "" );
idCVar net_ip( "net_ip", "localhost", 0, "local IP address" );
static struct sockaddr_in socksRelayAddr;
static SOCKET ip_socket;
static SOCKET socks_socket;
static char socksBuf[4096];
typedef struct {
unsigned long ip;
unsigned long mask;
char addr[16];
} net_interface;
#define MAX_INTERFACES 32
int num_interfaces = 0;
net_interface netint[MAX_INTERFACES];
/*
================================================================================================
Free Functions
================================================================================================
*/
/*
========================
NET_ErrorString
========================
*/
char *NET_ErrorString() {
int code;
code = WSAGetLastError();
switch( code ) {
case WSAEINTR: return "WSAEINTR";
case WSAEBADF: return "WSAEBADF";
case WSAEACCES: return "WSAEACCES";
case WSAEDISCON: return "WSAEDISCON";
case WSAEFAULT: return "WSAEFAULT";
case WSAEINVAL: return "WSAEINVAL";
case WSAEMFILE: return "WSAEMFILE";
case WSAEWOULDBLOCK: return "WSAEWOULDBLOCK";
case WSAEINPROGRESS: return "WSAEINPROGRESS";
case WSAEALREADY: return "WSAEALREADY";
case WSAENOTSOCK: return "WSAENOTSOCK";
case WSAEDESTADDRREQ: return "WSAEDESTADDRREQ";
case WSAEMSGSIZE: return "WSAEMSGSIZE";
case WSAEPROTOTYPE: return "WSAEPROTOTYPE";
case WSAENOPROTOOPT: return "WSAENOPROTOOPT";
case WSAEPROTONOSUPPORT: return "WSAEPROTONOSUPPORT";
case WSAESOCKTNOSUPPORT: return "WSAESOCKTNOSUPPORT";
case WSAEOPNOTSUPP: return "WSAEOPNOTSUPP";
case WSAEPFNOSUPPORT: return "WSAEPFNOSUPPORT";
case WSAEAFNOSUPPORT: return "WSAEAFNOSUPPORT";
case WSAEADDRINUSE: return "WSAEADDRINUSE";
case WSAEADDRNOTAVAIL: return "WSAEADDRNOTAVAIL";
case WSAENETDOWN: return "WSAENETDOWN";
case WSAENETUNREACH: return "WSAENETUNREACH";
case WSAENETRESET: return "WSAENETRESET";
case WSAECONNABORTED: return "WSAECONNABORTED";
case WSAECONNRESET: return "WSAECONNRESET";
case WSAENOBUFS: return "WSAENOBUFS";
case WSAEISCONN: return "WSAEISCONN";
case WSAENOTCONN: return "WSAENOTCONN";
case WSAESHUTDOWN: return "WSAESHUTDOWN";
case WSAETOOMANYREFS: return "WSAETOOMANYREFS";
case WSAETIMEDOUT: return "WSAETIMEDOUT";
case WSAECONNREFUSED: return "WSAECONNREFUSED";
case WSAELOOP: return "WSAELOOP";
case WSAENAMETOOLONG: return "WSAENAMETOOLONG";
case WSAEHOSTDOWN: return "WSAEHOSTDOWN";
case WSASYSNOTREADY: return "WSASYSNOTREADY";
case WSAVERNOTSUPPORTED: return "WSAVERNOTSUPPORTED";
case WSANOTINITIALISED: return "WSANOTINITIALISED";
case WSAHOST_NOT_FOUND: return "WSAHOST_NOT_FOUND";
case WSATRY_AGAIN: return "WSATRY_AGAIN";
case WSANO_RECOVERY: return "WSANO_RECOVERY";
case WSANO_DATA: return "WSANO_DATA";
default: return "NO ERROR";
}
}
/*
========================
Net_NetadrToSockadr
========================
*/
void Net_NetadrToSockadr( const netadr_t *a, sockaddr_in *s ) {
memset( s, 0, sizeof(*s) );
if ( a->type == NA_BROADCAST ) {
s->sin_family = AF_INET;
s->sin_addr.s_addr = INADDR_BROADCAST;
} else if ( a->type == NA_IP || a->type == NA_LOOPBACK ) {
s->sin_family = AF_INET;
s->sin_addr.s_addr = *(int *)a->ip;
}
s->sin_port = htons( (short)a->port );
}
/*
========================
Net_SockadrToNetadr
========================
*/
void Net_SockadrToNetadr( sockaddr_in *s, netadr_t *a ) {
unsigned int ip;
if ( s->sin_family == AF_INET ) {
ip = s->sin_addr.s_addr;
*(unsigned int *)a->ip = ip;
a->port = htons( s->sin_port );
// we store in network order, that loopback test is host order..
ip = ntohl( ip );
if ( ip == INADDR_LOOPBACK ) {
a->type = NA_LOOPBACK;
} else {
a->type = NA_IP;
}
}
}
/*
========================
Net_ExtractPort
========================
*/
static bool Net_ExtractPort( const char *src, char *buf, int bufsize, int *port ) {
char *p;
strncpy( buf, src, bufsize );
p = buf; p += Min( bufsize - 1, idStr::Length( src ) ); *p = '\0';
p = strchr( buf, ':' );
if ( !p ) {
return false;
}
*p = '\0';
*port = strtol( p+1, NULL, 10 );
if ( errno == ERANGE ) {
return false;
}
return true;
}
/*
========================
Net_StringToSockaddr
========================
*/
static bool Net_StringToSockaddr( const char *s, sockaddr_in *sadr, bool doDNSResolve ) {
struct hostent *h;
char buf[256];
int port;
memset( sadr, 0, sizeof( *sadr ) );
sadr->sin_family = AF_INET;
sadr->sin_port = 0;
if( s[0] >= '0' && s[0] <= '9' ) {
unsigned long ret = inet_addr(s);
if ( ret != INADDR_NONE ) {
*(int *)&sadr->sin_addr = ret;
} else {
// check for port
if ( !Net_ExtractPort( s, buf, sizeof( buf ), &port ) ) {
return false;
}
ret = inet_addr( buf );
if ( ret == INADDR_NONE ) {
return false;
}
*(int *)&sadr->sin_addr = ret;
sadr->sin_port = htons( port );
}
} else if ( doDNSResolve ) {
// try to remove the port first, otherwise the DNS gets confused into multiple timeouts
// failed or not failed, buf is expected to contain the appropriate host to resolve
if ( Net_ExtractPort( s, buf, sizeof( buf ), &port ) ) {
sadr->sin_port = htons( port );
}
h = gethostbyname( buf );
if ( h == 0 ) {
return false;
}
*(int *)&sadr->sin_addr = *(int *)h->h_addr_list[0];
}
return true;
}
/*
========================
NET_IPSocket
========================
*/
int NET_IPSocket( const char *net_interface, int port, netadr_t *bound_to ) {
SOCKET newsocket;
sockaddr_in address;
unsigned long _true = 1;
int i = 1;
int err;
if ( port != PORT_ANY ) {
if( net_interface ) {
idLib::Printf( "Opening IP socket: %s:%i\n", net_interface, port );
} else {
idLib::Printf( "Opening IP socket: localhost:%i\n", port );
}
}
if( ( newsocket = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ) ) == INVALID_SOCKET ) {
err = WSAGetLastError();
if( err != WSAEAFNOSUPPORT ) {
idLib::Printf( "WARNING: UDP_OpenSocket: socket: %s\n", NET_ErrorString() );
}
return 0;
}
// make it non-blocking
if( ioctlsocket( newsocket, FIONBIO, &_true ) == SOCKET_ERROR ) {
idLib::Printf( "WARNING: UDP_OpenSocket: ioctl FIONBIO: %s\n", NET_ErrorString() );
closesocket( newsocket );
return 0;
}
// make it broadcast capable
if( setsockopt( newsocket, SOL_SOCKET, SO_BROADCAST, (char *)&i, sizeof(i) ) == SOCKET_ERROR ) {
idLib::Printf( "WARNING: UDP_OpenSocket: setsockopt SO_BROADCAST: %s\n", NET_ErrorString() );
closesocket( newsocket );
return 0;
}
if( !net_interface || !net_interface[0] || !idStr::Icmp( net_interface, "localhost" ) ) {
address.sin_addr.s_addr = INADDR_ANY;
}
else {
Net_StringToSockaddr( net_interface, &address, true );
}
if( port == PORT_ANY ) {
address.sin_port = 0;
}
else {
address.sin_port = htons( (short)port );
}
address.sin_family = AF_INET;
if( bind( newsocket, (const sockaddr *)&address, sizeof(address) ) == SOCKET_ERROR ) {
idLib::Printf( "WARNING: UDP_OpenSocket: bind: %s\n", NET_ErrorString() );
closesocket( newsocket );
return 0;
}
// if the port was PORT_ANY, we need to query again to know the real port we got bound to
// ( this used to be in idUDP::InitForPort )
if ( bound_to ) {
int len = sizeof( address );
getsockname( newsocket, (sockaddr *)&address, &len );
Net_SockadrToNetadr( &address, bound_to );
}
return newsocket;
}
/*
========================
NET_OpenSocks
========================
*/
void NET_OpenSocks( int port ) {
sockaddr_in address;
struct hostent *h;
int len;
bool rfc1929;
unsigned char buf[64];
usingSocks = false;
idLib::Printf( "Opening connection to SOCKS server.\n" );
if ( ( socks_socket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) == INVALID_SOCKET ) {
idLib::Printf( "WARNING: NET_OpenSocks: socket: %s\n", NET_ErrorString() );
return;
}
h = gethostbyname( net_socksServer.GetString() );
if ( h == NULL ) {
idLib::Printf( "WARNING: NET_OpenSocks: gethostbyname: %s\n", NET_ErrorString() );
return;
}
if ( h->h_addrtype != AF_INET ) {
idLib::Printf( "WARNING: NET_OpenSocks: gethostbyname: address type was not AF_INET\n" );
return;
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = *(int *)h->h_addr_list[0];
address.sin_port = htons( (short)net_socksPort.GetInteger() );
if ( connect( socks_socket, (sockaddr *)&address, sizeof( address ) ) == SOCKET_ERROR ) {
idLib::Printf( "NET_OpenSocks: connect: %s\n", NET_ErrorString() );
return;
}
// send socks authentication handshake
if ( *net_socksUsername.GetString() || *net_socksPassword.GetString() ) {
rfc1929 = true;
}
else {
rfc1929 = false;
}
buf[0] = 5; // SOCKS version
// method count
if ( rfc1929 ) {
buf[1] = 2;
len = 4;
}
else {
buf[1] = 1;
len = 3;
}
buf[2] = 0; // method #1 - method id #00: no authentication
if ( rfc1929 ) {
buf[2] = 2; // method #2 - method id #02: username/password
}
if ( send( socks_socket, (const char *)buf, len, 0 ) == SOCKET_ERROR ) {
idLib::Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() );
return;
}
// get the response
len = recv( socks_socket, (char *)buf, 64, 0 );
if ( len == SOCKET_ERROR ) {
idLib::Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() );
return;
}
if ( len != 2 || buf[0] != 5 ) {
idLib::Printf( "NET_OpenSocks: bad response\n" );
return;
}
switch( buf[1] ) {
case 0: // no authentication
break;
case 2: // username/password authentication
break;
default:
idLib::Printf( "NET_OpenSocks: request denied\n" );
return;
}
// do username/password authentication if needed
if ( buf[1] == 2 ) {
int ulen;
int plen;
// build the request
ulen = idStr::Length( net_socksUsername.GetString() );
plen = idStr::Length( net_socksPassword.GetString() );
buf[0] = 1; // username/password authentication version
buf[1] = ulen;
if ( ulen ) {
memcpy( &buf[2], net_socksUsername.GetString(), ulen );
}
buf[2 + ulen] = plen;
if ( plen ) {
memcpy( &buf[3 + ulen], net_socksPassword.GetString(), plen );
}
// send it
if ( send( socks_socket, (const char *)buf, 3 + ulen + plen, 0 ) == SOCKET_ERROR ) {
idLib::Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() );
return;
}
// get the response
len = recv( socks_socket, (char *)buf, 64, 0 );
if ( len == SOCKET_ERROR ) {
idLib::Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() );
return;
}
if ( len != 2 || buf[0] != 1 ) {
idLib::Printf( "NET_OpenSocks: bad response\n" );
return;
}
if ( buf[1] != 0 ) {
idLib::Printf( "NET_OpenSocks: authentication failed\n" );
return;
}
}
// send the UDP associate request
buf[0] = 5; // SOCKS version
buf[1] = 3; // command: UDP associate
buf[2] = 0; // reserved
buf[3] = 1; // address type: IPV4
*(int *)&buf[4] = INADDR_ANY;
*(short *)&buf[8] = htons( (short)port ); // port
if ( send( socks_socket, (const char *)buf, 10, 0 ) == SOCKET_ERROR ) {
idLib::Printf( "NET_OpenSocks: send: %s\n", NET_ErrorString() );
return;
}
// get the response
len = recv( socks_socket, (char *)buf, 64, 0 );
if( len == SOCKET_ERROR ) {
idLib::Printf( "NET_OpenSocks: recv: %s\n", NET_ErrorString() );
return;
}
if( len < 2 || buf[0] != 5 ) {
idLib::Printf( "NET_OpenSocks: bad response\n" );
return;
}
// check completion code
if( buf[1] != 0 ) {
idLib::Printf( "NET_OpenSocks: request denied: %i\n", buf[1] );
return;
}
if( buf[3] != 1 ) {
idLib::Printf( "NET_OpenSocks: relay address is not IPV4: %i\n", buf[3] );
return;
}
socksRelayAddr.sin_family = AF_INET;
socksRelayAddr.sin_addr.s_addr = *(int *)&buf[4];
socksRelayAddr.sin_port = *(short *)&buf[8];
memset( socksRelayAddr.sin_zero, 0, sizeof( socksRelayAddr.sin_zero ) );
usingSocks = true;
}
/*
========================
Net_WaitForData
========================
*/
bool Net_WaitForData( int netSocket, int timeout ) {
int ret;
fd_set set;
struct timeval tv;
if ( !netSocket ) {
return false;
}
if ( timeout < 0 ) {
return true;
}
FD_ZERO( &set );
FD_SET( static_cast<unsigned int>( netSocket ), &set );
tv.tv_sec = 0;
tv.tv_usec = timeout * 1000;
ret = select( netSocket + 1, &set, NULL, NULL, &tv );
if ( ret == -1 ) {
idLib::Printf( "Net_WaitForData select(): %s\n", strerror( errno ) );
return false;
}
// timeout with no data
if ( ret == 0 ) {
return false;
}
return true;
}
/*
========================
Net_GetUDPPacket
========================
*/
bool Net_GetUDPPacket( int netSocket, netadr_t &net_from, char *data, int &size, int maxSize ) {
int ret;
sockaddr_in from;
int fromlen;
int err;
if ( !netSocket ) {
return false;
}
fromlen = sizeof(from);
ret = recvfrom( netSocket, data, maxSize, 0, (sockaddr *)&from, &fromlen );
if ( ret == SOCKET_ERROR ) {
err = WSAGetLastError();
if ( err == WSAEWOULDBLOCK || err == WSAECONNRESET ) {
return false;
}
char buf[1024];
sprintf( buf, "Net_GetUDPPacket: %s\n", NET_ErrorString() );
idLib::Printf( buf );
return false;
}
if ( static_cast<unsigned int>( netSocket ) == ip_socket ) {
memset( from.sin_zero, 0, sizeof( from.sin_zero ) );
}
if ( usingSocks && static_cast<unsigned int>( netSocket ) == ip_socket && memcmp( &from, &socksRelayAddr, fromlen ) == 0 ) {
if ( ret < 10 || data[0] != 0 || data[1] != 0 || data[2] != 0 || data[3] != 1 ) {
return false;
}
net_from.type = NA_IP;
net_from.ip[0] = data[4];
net_from.ip[1] = data[5];
net_from.ip[2] = data[6];
net_from.ip[3] = data[7];
net_from.port = *(short *)&data[8];
memmove( data, &data[10], ret - 10 );
} else {
Net_SockadrToNetadr( &from, &net_from );
}
if ( ret > maxSize ) {
char buf[1024];
sprintf( buf, "Net_GetUDPPacket: oversize packet from %s\n", Sys_NetAdrToString( net_from ) );
idLib::Printf( buf );
return false;
}
size = ret;
return true;
}
/*
========================
Net_SendUDPPacket
========================
*/
void Net_SendUDPPacket( int netSocket, int length, const void *data, const netadr_t to ) {
int ret;
sockaddr_in addr;
if ( !netSocket ) {
return;
}
Net_NetadrToSockadr( &to, &addr );
if ( usingSocks && to.type == NA_IP ) {
socksBuf[0] = 0; // reserved
socksBuf[1] = 0;
socksBuf[2] = 0; // fragment (not fragmented)
socksBuf[3] = 1; // address type: IPV4
*(int *)&socksBuf[4] = addr.sin_addr.s_addr;
*(short *)&socksBuf[8] = addr.sin_port;
memcpy( &socksBuf[10], data, length );
ret = sendto( netSocket, socksBuf, length+10, 0, (sockaddr *)&socksRelayAddr, sizeof(socksRelayAddr) );
} else {
ret = sendto( netSocket, (const char *)data, length, 0, (sockaddr *)&addr, sizeof(addr) );
}
if ( ret == SOCKET_ERROR ) {
int err = WSAGetLastError();
// some PPP links do not allow broadcasts and return an error
if ( ( err == WSAEADDRNOTAVAIL ) && ( to.type == NA_BROADCAST ) ) {
return;
}
// NOTE: WSAEWOULDBLOCK used to be silently ignored,
// but that means the packet will be dropped so I don't feel it's a good thing to ignore
idLib::Printf( "UDP sendto error - packet dropped: %s\n", NET_ErrorString() );
}
}
/*
========================
Sys_InitNetworking
========================
*/
void Sys_InitNetworking() {
int r;
if ( winsockInitialized ) {
return;
}
r = WSAStartup( MAKEWORD( 1, 1 ), &winsockdata );
if( r ) {
idLib::Printf( "WARNING: Winsock initialization failed, returned %d\n", r );
return;
}
winsockInitialized = true;
idLib::Printf( "Winsock Initialized\n" );
PIP_ADAPTER_INFO pAdapterInfo;
PIP_ADAPTER_INFO pAdapter = NULL;
DWORD dwRetVal = 0;
PIP_ADDR_STRING pIPAddrString;
ULONG ulOutBufLen;
bool foundloopback;
num_interfaces = 0;
foundloopback = false;
pAdapterInfo = (IP_ADAPTER_INFO *)malloc( sizeof( IP_ADAPTER_INFO ) );
if( !pAdapterInfo ) {
idLib::FatalError( "Sys_InitNetworking: Couldn't malloc( %d )", sizeof( IP_ADAPTER_INFO ) );
}
ulOutBufLen = sizeof( IP_ADAPTER_INFO );
// Make an initial call to GetAdaptersInfo to get
// the necessary size into the ulOutBufLen variable
if( GetAdaptersInfo( pAdapterInfo, &ulOutBufLen ) == ERROR_BUFFER_OVERFLOW ) {
free( pAdapterInfo );
pAdapterInfo = (IP_ADAPTER_INFO *)malloc( ulOutBufLen );
if( !pAdapterInfo ) {
idLib::FatalError( "Sys_InitNetworking: Couldn't malloc( %ld )", ulOutBufLen );
}
}
if( ( dwRetVal = GetAdaptersInfo( pAdapterInfo, &ulOutBufLen) ) != NO_ERROR ) {
// happens if you have no network connection
idLib::Printf( "Sys_InitNetworking: GetAdaptersInfo failed (%ld).\n", dwRetVal );
} else {
pAdapter = pAdapterInfo;
while( pAdapter ) {
idLib::Printf( "Found interface: %s %s - ", pAdapter->AdapterName, pAdapter->Description );
pIPAddrString = &pAdapter->IpAddressList;
while( pIPAddrString ) {
unsigned long ip_a, ip_m;
if( !idStr::Icmp( "127.0.0.1", pIPAddrString->IpAddress.String ) ) {
foundloopback = true;
}
ip_a = ntohl( inet_addr( pIPAddrString->IpAddress.String ) );
ip_m = ntohl( inet_addr( pIPAddrString->IpMask.String ) );
//skip null netmasks
if( !ip_m ) {
idLib::Printf( "%s NULL netmask - skipped\n", pIPAddrString->IpAddress.String );
pIPAddrString = pIPAddrString->Next;
continue;
}
idLib::Printf( "%s/%s\n", pIPAddrString->IpAddress.String, pIPAddrString->IpMask.String );
netint[num_interfaces].ip = ip_a;
netint[num_interfaces].mask = ip_m;
idStr::Copynz( netint[num_interfaces].addr, pIPAddrString->IpAddress.String, sizeof( netint[num_interfaces].addr ) );
num_interfaces++;
if( num_interfaces >= MAX_INTERFACES ) {
idLib::Printf( "Sys_InitNetworking: MAX_INTERFACES(%d) hit.\n", MAX_INTERFACES );
free( pAdapterInfo );
return;
}
pIPAddrString = pIPAddrString->Next;
}
pAdapter = pAdapter->Next;
}
}
// for some retarded reason, win32 doesn't count loopback as an adapter...
if( !foundloopback && num_interfaces < MAX_INTERFACES ) {
idLib::Printf( "Sys_InitNetworking: adding loopback interface\n" );
netint[num_interfaces].ip = ntohl( inet_addr( "127.0.0.1" ) );
netint[num_interfaces].mask = ntohl( inet_addr( "255.0.0.0" ) );
num_interfaces++;
}
free( pAdapterInfo );
}
/*
========================
Sys_ShutdownNetworking
========================
*/
void Sys_ShutdownNetworking() {
if ( !winsockInitialized ) {
return;
}
WSACleanup();
winsockInitialized = false;
}
/*
========================
Sys_StringToNetAdr
========================
*/
bool Sys_StringToNetAdr( const char *s, netadr_t *a, bool doDNSResolve ) {
sockaddr_in sadr;
if ( !Net_StringToSockaddr( s, &sadr, doDNSResolve ) ) {
return false;
}
Net_SockadrToNetadr( &sadr, a );
return true;
}
/*
========================
Sys_NetAdrToString
========================
*/
const char *Sys_NetAdrToString( const netadr_t a ) {
static int index = 0;
static char buf[ 4 ][ 64 ]; // flip/flop
char *s;
s = buf[index];
index = (index + 1) & 3;
if ( a.type == NA_LOOPBACK ) {
if ( a.port ) {
idStr::snPrintf( s, 64, "localhost:%i", a.port );
} else {
idStr::snPrintf( s, 64, "localhost" );
}
} else if ( a.type == NA_IP ) {
idStr::snPrintf( s, 64, "%i.%i.%i.%i:%i", a.ip[0], a.ip[1], a.ip[2], a.ip[3], a.port );
}
return s;
}
/*
========================
Sys_IsLANAddress
========================
*/
bool Sys_IsLANAddress( const netadr_t adr ) {
if ( adr.type == NA_LOOPBACK ) {
return true;
}
if ( adr.type != NA_IP ) {
return false;
}
if ( num_interfaces ) {
int i;
unsigned long *p_ip;
unsigned long ip;
p_ip = (unsigned long *)&adr.ip[0];
ip = ntohl( *p_ip );
for( i = 0; i < num_interfaces; i++ ) {
if( ( netint[i].ip & netint[i].mask ) == ( ip & netint[i].mask ) ) {
return true;
}
}
}
return false;
}
/*
========================
Sys_CompareNetAdrBase
Compares without the port.
========================
*/
bool Sys_CompareNetAdrBase( const netadr_t a, const netadr_t b ) {
if ( a.type != b.type ) {
return false;
}
if ( a.type == NA_LOOPBACK ) {
if ( a.port == b.port ) {
return true;
}
return false;
}
if ( a.type == NA_IP ) {
if ( a.ip[0] == b.ip[0] && a.ip[1] == b.ip[1] && a.ip[2] == b.ip[2] && a.ip[3] == b.ip[3] ) {
return true;
}
return false;
}
idLib::Printf( "Sys_CompareNetAdrBase: bad address type\n" );
return false;
}
/*
========================
Sys_GetLocalIPCount
========================
*/
int Sys_GetLocalIPCount() {
return num_interfaces;
}
/*
========================
Sys_GetLocalIP
========================
*/
const char * Sys_GetLocalIP( int i ) {
if ( ( i < 0 ) || ( i >= num_interfaces ) ) {
return NULL;
}
return netint[i].addr;
}
/*
================================================================================================
idUDP
================================================================================================
*/
/*
========================
idUDP::idUDP
========================
*/
idUDP::idUDP() {
netSocket = 0;
memset( &bound_to, 0, sizeof( bound_to ) );
silent = false;
packetsRead = 0;
bytesRead = 0;
packetsWritten = 0;
bytesWritten = 0;
}
/*
========================
idUDP::~idUDP
========================
*/
idUDP::~idUDP() {
Close();
}
/*
========================
idUDP::InitForPort
========================
*/
bool idUDP::InitForPort( int portNumber ) {
netSocket = NET_IPSocket( net_ip.GetString(), portNumber, &bound_to );
if ( netSocket <= 0 ) {
netSocket = 0;
memset( &bound_to, 0, sizeof( bound_to ) );
return false;
}
return true;
}
/*
========================
idUDP::Close
========================
*/
void idUDP::Close() {
if ( netSocket ) {
closesocket( netSocket );
netSocket = 0;
memset( &bound_to, 0, sizeof( bound_to ) );
}
}
/*
========================
idUDP::GetPacket
========================
*/
bool idUDP::GetPacket( netadr_t &from, void *data, int &size, int maxSize ) {
bool ret;
while ( 1 ) {
ret = Net_GetUDPPacket( netSocket, from, (char *)data, size, maxSize );
if ( !ret ) {
break;
}
packetsRead++;
bytesRead += size;
break;
}
return ret;
}
/*
========================
idUDP::GetPacketBlocking
========================
*/
bool idUDP::GetPacketBlocking( netadr_t &from, void *data, int &size, int maxSize, int timeout ) {
if ( !Net_WaitForData( netSocket, timeout ) ) {
return false;
}
if ( GetPacket( from, data, size, maxSize ) ) {
return true;
}
return false;
}
/*
========================
idUDP::SendPacket
========================
*/
void idUDP::SendPacket( const netadr_t to, const void *data, int size ) {
if ( to.type == NA_BAD ) {
idLib::Warning( "idUDP::SendPacket: bad address type NA_BAD - ignored" );
return;
}
packetsWritten++;
bytesWritten += size;
if ( silent ) {
return;
}
Net_SendUDPPacket( netSocket, size, data, to );
}
File diff suppressed because it is too large Load Diff
+698
View File
@@ -0,0 +1,698 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
#include "../sys_session_local.h"
#include "../sys_savegame.h"
idCVar savegame_winInduceDelay( "savegame_winInduceDelay", "0", CVAR_INTEGER, "on windows, this is a delay induced before any file operation occurs" );
extern idCVar fs_savepath;
extern idCVar saveGame_checksum;
extern idCVar savegame_error;
#define SAVEGAME_SENTINAL 0x12358932
/*
========================
void Sys_ExecuteSavegameCommandAsync
========================
*/
void Sys_ExecuteSavegameCommandAsyncImpl( idSaveLoadParms * savegameParms ) {
assert( savegameParms != NULL );
session->GetSaveGameManager().GetSaveGameThread().data.saveLoadParms = savegameParms;
if ( session->GetSaveGameManager().GetSaveGameThread().GetThreadHandle() == 0 ) {
session->GetSaveGameManager().GetSaveGameThread().StartWorkerThread( "Savegame", CORE_ANY );
}
session->GetSaveGameManager().GetSaveGameThread().SignalWork();
}
/*
========================
idLocalUser * GetLocalUserFromUserId
========================
*/
idLocalUserWin * GetLocalUserFromSaveParms( const saveGameThreadArgs_t & data ) {
if ( ( data.saveLoadParms != NULL) && ( data.saveLoadParms->inputDeviceId >= 0 ) ) {
idLocalUser * user = session->GetSignInManager().GetLocalUserByInputDevice( data.saveLoadParms->inputDeviceId );
if ( user != NULL ) {
idLocalUserWin * userWin = static_cast< idLocalUserWin * >( user );
if ( userWin != NULL && data.saveLoadParms->userId == idStr::Hash( userWin->GetGamerTag() ) ) {
return userWin;
}
}
}
return NULL;
}
/*
========================
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;
}
/*
========================
idSaveGameThread::Delete
This deletes a complete savegame directory
========================
*/
int idSaveGameThread::Delete() {
idSaveLoadParms * callback = data.saveLoadParms;
idStr saveFolder = "savegame";
saveFolder.AppendPath( callback->directory );
int ret = ERROR_SUCCESS;
if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) {
idFileList * files = fileSystem->ListFilesTree( saveFolder, "/|*" );
for ( int i = 0; i < files->GetNumFiles() && !callback->cancelled; i++ ) {
fileSystem->RemoveFile( files->GetFile( i ) );
}
fileSystem->FreeFileList( files );
fileSystem->RemoveDir( saveFolder );
} else {
callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND;
ret = -1;
}
if ( data.saveLoadParms->cancelled ) {
data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED;
}
return ret;
}
/*
========================
idSaveGameThread::Enumerate
========================
*/
int idSaveGameThread::Enumerate() {
idSaveLoadParms * callback = data.saveLoadParms;
idStr saveFolder = "savegame";
callback->detailList.Clear();
int ret = ERROR_SUCCESS;
if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) {
idFileList * files = fileSystem->ListFilesTree( saveFolder, SAVEGAME_DETAILS_FILENAME );
const idStrList & fileList = files->GetList();
for ( int i = 0; i < fileList.Num() && !callback->cancelled; i++ ) {
idSaveGameDetails * details = callback->detailList.Alloc();
// We have more folders on disk than we have room in our save detail list, stop trying to read them in and continue with what we have
if ( details == NULL ) {
break;
}
idStr directory = fileList[i];
idFile * file = fileSystem->OpenFileRead( directory.c_str() );
if ( file != NULL ) {
// Read the DETAIL file for the enumerated data
if ( callback->mode & SAVEGAME_MBF_READ_DETAILS ) {
if ( !SavegameReadDetailsFromFile( file, *details ) ) {
details->damaged = true;
ret = -1;
}
}
// Use the date from the directory
WIN32_FILE_ATTRIBUTE_DATA attrData;
BOOL attrRet = GetFileAttributesEx( file->GetFullPath(), GetFileExInfoStandard, &attrData );
delete file;
if ( attrRet == TRUE ) {
FILETIME lastWriteTime = attrData.ftLastWriteTime;
const ULONGLONG second = 10000000L; // One second = 10,000,000 * 100 nsec
SYSTEMTIME base_st = { 1970, 1, 0, 1, 0, 0, 0, 0 };
ULARGE_INTEGER itime;
FILETIME base_ft;
BOOL success = SystemTimeToFileTime( &base_st, &base_ft );
itime.QuadPart = ((ULARGE_INTEGER *)&lastWriteTime)->QuadPart;
if ( success ) {
itime.QuadPart -= ((ULARGE_INTEGER *)&base_ft)->QuadPart;
} else {
// Hard coded number of 100-nanosecond units from 1/1/1601 to 1/1/1970
itime.QuadPart -= 116444736000000000LL;
}
itime.QuadPart /= second;
details->date = itime.QuadPart;
}
} else {
details->damaged = true;
}
// populate the game details struct
directory = directory.StripFilename();
details->slotName = directory.c_str() + saveFolder.Length() + 1; // Strip off the prefix too
// JDC: I hit this all the time assert( fileSystem->IsFolder( directory.c_str(), "fs_savePath" ) == FOLDER_YES );
}
fileSystem->FreeFileList( files );
} else {
callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND;
ret = -3;
}
if ( data.saveLoadParms->cancelled ) {
data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED;
}
return ret;
}
/*
========================
idSaveGameThread::EnumerateFiles
========================
*/
int idSaveGameThread::EnumerateFiles() {
idSaveLoadParms * callback = data.saveLoadParms;
idStr folder = "savegame";
folder.AppendPath( callback->directory );
callback->files.Clear();
int ret = ERROR_SUCCESS;
if ( fileSystem->IsFolder( folder, "fs_savePath" ) == FOLDER_YES ) {
// get listing of all the files, but filter out below
idFileList * files = fileSystem->ListFilesTree( folder, "*.*" );
// look for the instance pattern
for ( int i = 0; i < files->GetNumFiles() && ret == 0 && !callback->cancelled; i++ ) {
idStr fullFilename = files->GetFile( i );
idStr filename = fullFilename;
filename.StripPath();
if ( filename.IcmpPrefix( callback->pattern ) != 0 ) {
continue;
}
if ( !callback->postPattern.IsEmpty() && filename.Right( callback->postPattern.Length() ).IcmpPrefix( callback->postPattern ) != 0 ) {
continue;
}
// Read the DETAIL file for the enumerated data
if ( callback->mode & SAVEGAME_MBF_READ_DETAILS ) {
idSaveGameDetails & details = callback->description;
idFile * uncompressed = fileSystem->OpenFileRead( fullFilename.c_str() );
if ( uncompressed == NULL ) {
details.damaged = true;
} else {
if ( !SavegameReadDetailsFromFile( uncompressed, details ) ) {
ret = -1;
}
delete uncompressed;
}
// populate the game details struct
details.slotName = callback->directory;
assert( fileSystem->IsFolder( details.slotName, "fs_savePath" ) == FOLDER_YES );
}
idFile_SaveGame * file = new (TAG_SAVEGAMES) idFile_SaveGame( filename, SAVEGAMEFILE_AUTO_DELETE );
callback->files.Append( file );
}
fileSystem->FreeFileList( files );
} else {
callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND;
ret = -3;
}
if ( data.saveLoadParms->cancelled ) {
data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED;
}
return ret;
}
/*
========================
idSaveGameThread::DeleteFiles
========================
*/
int idSaveGameThread::DeleteFiles() {
idSaveLoadParms * callback = data.saveLoadParms;
idStr folder = "savegame";
folder.AppendPath( callback->directory );
// delete the explicitly requested files first
for ( int j = 0; j < callback->files.Num() && !callback->cancelled; ++j ) {
idFile_SaveGame * file = callback->files[j];
idStr fullpath = folder;
fullpath.AppendPath( file->GetName() );
fileSystem->RemoveFile( fullpath );
}
int ret = ERROR_SUCCESS;
if ( fileSystem->IsFolder( folder, "fs_savePath" ) == FOLDER_YES ) {
// get listing of all the files, but filter out below
idFileList * files = fileSystem->ListFilesTree( folder, "*.*" );
// look for the instance pattern
for ( int i = 0; i < files->GetNumFiles() && !callback->cancelled; i++ ) {
idStr filename = files->GetFile( i );
filename.StripPath();
// If there are post/pre patterns to match, make sure we adhere to the patterns
if ( callback->pattern.IsEmpty() || ( filename.IcmpPrefix( callback->pattern ) != 0 ) ) {
continue;
}
if ( callback->postPattern.IsEmpty() || ( filename.Right( callback->postPattern.Length() ).IcmpPrefix( callback->postPattern ) != 0 ) ) {
continue;
}
fileSystem->RemoveFile( files->GetFile( i ) );
}
fileSystem->FreeFileList( files );
} else {
callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND;
ret = -3;
}
if ( data.saveLoadParms->cancelled ) {
data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED;
}
return ret;
}
/*
========================
idSaveGameThread::DeleteAll
This deletes all savegame directories
========================
*/
int idSaveGameThread::DeleteAll() {
idSaveLoadParms * callback = data.saveLoadParms;
idStr saveFolder = "savegame";
int ret = ERROR_SUCCESS;
if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) {
idFileList * files = fileSystem->ListFilesTree( saveFolder, "/|*" );
// remove directories after files
for ( int i = 0; i < files->GetNumFiles() && !callback->cancelled; i++ ) {
// contained files should always be first
if ( fileSystem->IsFolder( files->GetFile( i ), "fs_savePath" ) == FOLDER_YES ) {
fileSystem->RemoveDir( files->GetFile( i ) );
} else {
fileSystem->RemoveFile( files->GetFile( i ) );
}
}
fileSystem->FreeFileList( files );
} else {
callback->errorCode = SAVEGAME_E_FOLDER_NOT_FOUND;
ret = -3;
}
if ( data.saveLoadParms->cancelled ) {
data.saveLoadParms->errorCode = SAVEGAME_E_CANCELLED;
}
return ret;
}
/*
========================
idSaveGameThread::Run
========================
*/
int idSaveGameThread::Run() {
int ret = ERROR_SUCCESS;
try {
idLocalUserWin * user = GetLocalUserFromSaveParms( data );
if ( user != NULL && !user->IsStorageDeviceAvailable() ) {
data.saveLoadParms->errorCode = SAVEGAME_E_UNABLE_TO_SELECT_STORAGE_DEVICE;
}
if ( savegame_winInduceDelay.GetInteger() > 0 ) {
Sys_Sleep( savegame_winInduceDelay.GetInteger() );
}
if ( data.saveLoadParms->mode & SAVEGAME_MBF_SAVE ) {
ret = Save();
} else if ( data.saveLoadParms->mode & SAVEGAME_MBF_LOAD ) {
ret = Load();
} else if ( data.saveLoadParms->mode & SAVEGAME_MBF_ENUMERATE ) {
ret = Enumerate();
} else if ( data.saveLoadParms->mode & SAVEGAME_MBF_DELETE_FOLDER ) {
ret = Delete();
} else if ( data.saveLoadParms->mode & SAVEGAME_MBF_DELETE_ALL_FOLDERS ) {
ret = DeleteAll();
} else if ( data.saveLoadParms->mode & SAVEGAME_MBF_DELETE_FILES ) {
ret = DeleteFiles();
} else if ( data.saveLoadParms->mode & SAVEGAME_MBF_ENUMERATE_FILES ) {
ret = EnumerateFiles();
}
// if something failed and no one set an error code, do it now.
if ( ret != 0 && data.saveLoadParms->errorCode == SAVEGAME_E_NONE ) {
data.saveLoadParms->errorCode = SAVEGAME_E_UNKNOWN;
}
} catch ( ... ) {
// if anything horrible happens, leave it up to the savegame processors to handle in PostProcess().
data.saveLoadParms->errorCode = SAVEGAME_E_UNKNOWN;
}
// Make sure to cancel any save game file pipelines.
if ( data.saveLoadParms->errorCode != SAVEGAME_E_NONE ) {
data.saveLoadParms->CancelSaveGameFilePipelines();
}
// Override error if cvar set
if ( savegame_error.GetInteger() != 0 ) {
data.saveLoadParms->errorCode = (saveGameError_t)savegame_error.GetInteger();
}
// Tell the waiting caller that we are done
data.saveLoadParms->callbackSignal.Raise();
return ret;
}
/*
========================
Sys_SaveGameCheck
========================
*/
void Sys_SaveGameCheck( bool & exists, bool & autosaveExists ) {
exists = false;
autosaveExists = false;
const idStr autosaveFolderStr = AddSaveFolderPrefix( SAVEGAME_AUTOSAVE_FOLDER, idSaveGameManager::PACKAGE_GAME );
const char * autosaveFolder = autosaveFolderStr.c_str();
const char * saveFolder = "savegame";
if ( fileSystem->IsFolder( saveFolder, "fs_savePath" ) == FOLDER_YES ) {
idFileList * files = fileSystem->ListFiles( saveFolder, "/" );
const idStrList & fileList = files->GetList();
idLib::PrintfIf( saveGame_verbose.GetBool(), "found %d savegames\n", fileList.Num() );
for ( int i = 0; i < fileList.Num(); i++ ) {
const char * directory = va( "%s/%s", saveFolder, fileList[i].c_str() );
if ( fileSystem->IsFolder( directory, "fs_savePath" ) == FOLDER_YES ) {
exists = true;
idLib::PrintfIf( saveGame_verbose.GetBool(), "found savegame: %s\n", fileList[i].c_str() );
if ( idStr::Icmp( fileList[i].c_str(), autosaveFolder ) == 0 ) {
autosaveExists = true;
break;
}
}
}
fileSystem->FreeFileList( files );
}
}
+668
View File
@@ -0,0 +1,668 @@
/*
================================================================================================
CONFIDENTIAL AND PROPRIETARY INFORMATION/NOT FOR DISCLOSURE WITHOUT WRITTEN PERMISSION
Copyright 2010 id Software LLC, a ZeniMax Media company. All Rights Reserved.
================================================================================================
*/
/*
================================================================================================
Contains the windows implementation of the network session
================================================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
#include "../../framework/Common_local.h"
#include "../sys_session_local.h"
#include "../sys_stats.h"
#include "../sys_savegame.h"
#include "../sys_lobby_backend_direct.h"
#include "../sys_voicechat.h"
#include "win_achievements.h"
#include "win_local.h"
/*
========================
Global variables
========================
*/
extern idCVar net_port;
/*
========================
idSessionLocalWin::idSessionLocalWin
========================
*/
class idSessionLocalWin : public idSessionLocal {
friend class idLobbyToSessionCBLocal;
public:
idSessionLocalWin();
virtual ~idSessionLocalWin();
// idSessionLocal interface
virtual void Initialize();
virtual void Shutdown();
virtual void InitializeSoundRelatedSystems();
virtual void ShutdownSoundRelatedSystems();
virtual void PlatformPump();
virtual void InviteFriends();
virtual void InviteParty();
virtual void ShowPartySessions();
virtual void ShowSystemMarketplaceUI() const;
virtual void ListServers( const idCallback & callback );
virtual void CancelListServers();
virtual int NumServers() const;
virtual const serverInfo_t * ServerInfo( int i ) const;
virtual void ConnectToServer( int i );
virtual void ShowServerGamerCardUI( int i );
virtual void ShowLobbyUserGamerCardUI( lobbyUserID_t lobbyUserID );
virtual void ShowOnlineSignin() {}
virtual void UpdateRichPresence() {}
virtual void CheckVoicePrivileges() {}
virtual bool ProcessInputEvent( const sysEvent_t * ev );
// System UI
virtual bool IsSystemUIShowing() const;
virtual void SetSystemUIShowing( bool show );
// Invites
virtual void HandleBootableInvite( int64 lobbyId = 0 );
virtual void ClearBootableInvite();
virtual void ClearPendingInvite();
virtual bool HasPendingBootableInvite();
virtual void SetDiscSwapMPInvite( void * parm );
virtual void * GetDiscSwapMPInviteParms();
virtual void EnumerateDownloadableContent();
virtual void HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType );
virtual void HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg );
// Leaderboards
virtual void LeaderboardUpload( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats, const idFile_Memory * attachment = NULL );
virtual void LeaderboardDownload( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int startingRank, int numRows, const idLeaderboardCallback & callback );
virtual void LeaderboardDownloadAttachment( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int64 attachmentID );
// Scoring (currently just for TrueSkill)
virtual void SetLobbyUserRelativeScore( lobbyUserID_t lobbyUserID, int relativeScore, int team ) {}
virtual void LeaderboardFlush();
virtual idNetSessionPort & GetPort( bool dedicated = false );
virtual idLobbyBackend * CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType );
virtual idLobbyBackend * FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType );
virtual idLobbyBackend * JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo , idLobbyBackend::lobbyBackendType_t lobbyType );
virtual void DestroyLobbyBackend( idLobbyBackend * lobbyBackend );
virtual void PumpLobbies();
virtual void JoinAfterSwap( void * joinID );
virtual bool GetLobbyAddressFromNetAddress( const netadr_t & netAddr, lobbyAddress_t & outAddr ) const;
virtual bool GetNetAddressFromLobbyAddress( const lobbyAddress_t & lobbyAddress, netadr_t & outNetAddr ) const;
public:
void Connect_f( const idCmdArgs &args );
private:
void EnsurePort();
idLobbyBackend * CreateLobbyInternal( idLobbyBackend::lobbyBackendType_t lobbyType );
idArray< idLobbyBackend *, 3 > lobbyBackends;
idNetSessionPort port;
bool canJoinLocalHost;
idLobbyToSessionCBLocal * lobbyToSessionCB;
};
idSessionLocalWin sessionLocalWin;
idSession * session = &sessionLocalWin;
/*
========================
idLobbyToSessionCBLocal
========================
*/
class idLobbyToSessionCBLocal : public idLobbyToSessionCB {
public:
idLobbyToSessionCBLocal( idSessionLocalWin * sessionLocalWin_ ) : sessionLocalWin( sessionLocalWin_ ) { }
virtual bool CanJoinLocalHost() const { sessionLocalWin->EnsurePort(); return sessionLocalWin->canJoinLocalHost; }
virtual class idLobbyBackend * GetLobbyBackend( idLobbyBackend::lobbyBackendType_t type ) const { return sessionLocalWin->lobbyBackends[ type ]; }
private:
idSessionLocalWin * sessionLocalWin;
};
idLobbyToSessionCBLocal lobbyToSessionCBLocal( &sessionLocalWin );
idLobbyToSessionCB * lobbyToSessionCB = &lobbyToSessionCBLocal;
class idVoiceChatMgrWin : public idVoiceChatMgr {
public:
virtual bool GetLocalChatDataInternal( int talkerIndex, byte * data, int & dataSize ) { return false; }
virtual void SubmitIncomingChatDataInternal( int talkerIndex, const byte * data, int dataSize ) { }
virtual bool TalkerHasData( int talkerIndex ) { return false; }
virtual bool RegisterTalkerInternal( int index ) { return true; }
virtual void UnregisterTalkerInternal( int index ) { }
};
/*
========================
idSessionLocalWin::idSessionLocalWin
========================
*/
idSessionLocalWin::idSessionLocalWin() {
signInManager = new (TAG_SYSTEM) idSignInManagerWin;
saveGameManager = new (TAG_SAVEGAMES) idSaveGameManager();
voiceChat = new (TAG_SYSTEM) idVoiceChatMgrWin();
lobbyToSessionCB = new (TAG_SYSTEM) idLobbyToSessionCBLocal( this );
canJoinLocalHost = false;
lobbyBackends.Zero();
}
/*
========================
idSessionLocalWin::idSessionLocalWin
========================
*/
idSessionLocalWin::~idSessionLocalWin() {
delete voiceChat;
delete lobbyToSessionCB;
}
/*
========================
idSessionLocalWin::Initialize
========================
*/
void idSessionLocalWin::Initialize() {
idSessionLocal::Initialize();
// The shipping path doesn't load title storage
// Instead, we inject values through code which is protected through steam DRM
titleStorageVars.Set( "MAX_PLAYERS_ALLOWED", "8" );
titleStorageLoaded = true;
// First-time check for downloadable content once game is launched
EnumerateDownloadableContent();
GetPartyLobby().Initialize( idLobby::TYPE_PARTY, sessionCallbacks );
GetGameLobby().Initialize( idLobby::TYPE_GAME, sessionCallbacks );
GetGameStateLobby().Initialize( idLobby::TYPE_GAME_STATE, sessionCallbacks );
achievementSystem = new (TAG_SYSTEM) idAchievementSystemWin();
achievementSystem->Init();
}
/*
========================
idSessionLocalWin::Shutdown
========================
*/
void idSessionLocalWin::Shutdown() {
NET_VERBOSE_PRINT( "NET: Shutdown\n" );
idSessionLocal::Shutdown();
MoveToMainMenu();
// Wait until we fully shutdown
while ( localState != STATE_IDLE && localState != STATE_PRESS_START ) {
Pump();
}
if ( achievementSystem != NULL ) {
achievementSystem->Shutdown();
delete achievementSystem;
achievementSystem = NULL;
}
}
/*
========================
idSessionLocalWin::InitializeSoundRelatedSystems
========================
*/
void idSessionLocalWin::InitializeSoundRelatedSystems() {
if ( voiceChat != NULL ) {
voiceChat->Init( NULL );
}
}
/*
========================
idSessionLocalWin::ShutdownSoundRelatedSystems
========================
*/
void idSessionLocalWin::ShutdownSoundRelatedSystems() {
if ( voiceChat != NULL ) {
voiceChat->Shutdown();
}
}
/*
========================
idSessionLocalWin::PlatformPump
========================
*/
void idSessionLocalWin::PlatformPump() {
}
/*
========================
idSessionLocalWin::InviteFriends
========================
*/
void idSessionLocalWin::InviteFriends() {
}
/*
========================
idSessionLocalWin::InviteParty
========================
*/
void idSessionLocalWin::InviteParty() {
}
/*
========================
idSessionLocalWin::ShowPartySessions
========================
*/
void idSessionLocalWin::ShowPartySessions() {
}
/*
========================
idSessionLocalWin::ShowSystemMarketplaceUI
========================
*/
void idSessionLocalWin::ShowSystemMarketplaceUI() const {
}
/*
========================
idSessionLocalWin::ListServers
========================
*/
void idSessionLocalWin::ListServers( const idCallback & callback ) {
ListServersCommon();
}
/*
========================
idSessionLocalWin::CancelListServers
========================
*/
void idSessionLocalWin::CancelListServers() {
}
/*
========================
idSessionLocalWin::NumServers
========================
*/
int idSessionLocalWin::NumServers() const {
return 0;
}
/*
========================
idSessionLocalWin::ServerInfo
========================
*/
const serverInfo_t * idSessionLocalWin::ServerInfo( int i ) const {
return NULL;
}
/*
========================
idSessionLocalWin::ConnectToServer
========================
*/
void idSessionLocalWin::ConnectToServer( int i ) {
}
/*
========================
idSessionLocalWin::Connect_f
========================
*/
void idSessionLocalWin::Connect_f( const idCmdArgs &args ) {
if ( args.Argc() < 2 ) {
idLib::Printf( "Usage: Connect to IP. Use with net_port. \n");
return;
}
Cancel();
if ( signInManager->GetMasterLocalUser() == NULL ) {
signInManager->RegisterLocalUser( 0 );
}
lobbyConnectInfo_t connectInfo;
Sys_StringToNetAdr( args.Argv(1), &connectInfo.netAddr, true );
connectInfo.netAddr.port = net_port.GetInteger();
ConnectAndMoveToLobby( GetPartyLobby(), connectInfo, false );
}
/*
========================
void Connect_f
========================
*/
CONSOLE_COMMAND( connect, "Connect to the specified IP", NULL ) {
sessionLocalWin.Connect_f( args );
}
/*
========================
idSessionLocalWin::ShowServerGamerCardUI
========================
*/
void idSessionLocalWin::ShowServerGamerCardUI( int i ) {
}
/*
========================
idSessionLocalWin::ShowLobbyUserGamerCardUI(
========================
*/
void idSessionLocalWin::ShowLobbyUserGamerCardUI( lobbyUserID_t lobbyUserID ) {
}
/*
========================
idSessionLocalWin::ProcessInputEvent
========================
*/
bool idSessionLocalWin::ProcessInputEvent( const sysEvent_t * ev ) {
if ( GetSignInManager().ProcessInputEvent( ev ) ) {
return true;
}
return false;
}
/*
========================
idSessionLocalWin::IsSystemUIShowing
========================
*/
bool idSessionLocalWin::IsSystemUIShowing() const {
return !win32.activeApp || isSysUIShowing; // If the user alt+tabs away, treat it the same as bringing up the steam overlay
}
/*
========================
idSessionLocalWin::SetSystemUIShowing
========================
*/
void idSessionLocalWin::SetSystemUIShowing( bool show ) {
isSysUIShowing = show;
}
/*
========================
idSessionLocalWin::HandleServerQueryRequest
========================
*/
void idSessionLocalWin::HandleServerQueryRequest( lobbyAddress_t & remoteAddr, idBitMsg & msg, int msgType ) {
NET_VERBOSE_PRINT( "HandleServerQueryRequest from %s\n", remoteAddr.ToString() );
}
/*
========================
idSessionLocalWin::HandleServerQueryAck
========================
*/
void idSessionLocalWin::HandleServerQueryAck( lobbyAddress_t & remoteAddr, idBitMsg & msg ) {
NET_VERBOSE_PRINT( "HandleServerQueryAck from %s\n", remoteAddr.ToString() );
}
/*
========================
idSessionLocalWin::ClearBootableInvite
========================
*/
void idSessionLocalWin::ClearBootableInvite() {
}
/*
========================
idSessionLocalWin::ClearPendingInvite
========================
*/
void idSessionLocalWin::ClearPendingInvite() {
}
/*
========================
idSessionLocalWin::HandleBootableInvite
========================
*/
void idSessionLocalWin::HandleBootableInvite( int64 lobbyId ) {
}
/*
========================
idSessionLocalWin::HasPendingBootableInvite
========================
*/
bool idSessionLocalWin::HasPendingBootableInvite() {
return false;
}
/*
========================
idSessionLocal::SetDiscSwapMPInvite
========================
*/
void idSessionLocalWin::SetDiscSwapMPInvite( void * parm ) {
}
/*
========================
idSessionLocal::GetDiscSwapMPInviteParms
========================
*/
void * idSessionLocalWin::GetDiscSwapMPInviteParms() {
return NULL;
}
/*
========================
idSessionLocalWin::EnumerateDownloadableContent
========================
*/
void idSessionLocalWin::EnumerateDownloadableContent() {
}
/*
========================
idSessionLocalWin::LeaderboardUpload
========================
*/
void idSessionLocalWin::LeaderboardUpload( lobbyUserID_t lobbyUserID, const leaderboardDefinition_t * leaderboard, const column_t * stats, const idFile_Memory * attachment ) {
}
/*
========================
idSessionLocalWin::LeaderboardFlush
========================
*/
void idSessionLocalWin::LeaderboardFlush() {
}
/*
========================
idSessionLocalWin::LeaderboardDownload
========================
*/
void idSessionLocalWin::LeaderboardDownload( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int startingRank, int numRows, const idLeaderboardCallback & callback ) {
}
/*
========================
idSessionLocalWin::LeaderboardDownloadAttachment
========================
*/
void idSessionLocalWin::LeaderboardDownloadAttachment( int sessionUserIndex, const leaderboardDefinition_t * leaderboard, int64 attachmentID ) {
}
/*
========================
idSessionLocalWin::EnsurePort
========================
*/
void idSessionLocalWin::EnsurePort() {
// Init the port using reqular windows sockets
if ( port.IsOpen() ) {
return; // Already initialized
}
if ( port.InitPort( net_port.GetInteger(), false ) ) {
canJoinLocalHost = false;
} else {
// Assume this is another instantiation on the same machine, and just init using any available port
port.InitPort( PORT_ANY, false );
canJoinLocalHost = true;
}
}
/*
========================
idSessionLocalWin::GetPort
========================
*/
idNetSessionPort & idSessionLocalWin::GetPort( bool dedicated ) {
EnsurePort();
return port;
}
/*
========================
idSessionLocalWin::CreateLobbyBackend
========================
*/
idLobbyBackend * idSessionLocalWin::CreateLobbyBackend( const idMatchParameters & p, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) {
idLobbyBackend * lobbyBackend = CreateLobbyInternal( lobbyType );
lobbyBackend->StartHosting( p, skillLevel, lobbyType );
return lobbyBackend;
}
/*
========================
idSessionLocalWin::FindLobbyBackend
========================
*/
idLobbyBackend * idSessionLocalWin::FindLobbyBackend( const idMatchParameters & p, int numPartyUsers, float skillLevel, idLobbyBackend::lobbyBackendType_t lobbyType ) {
idLobbyBackend * lobbyBackend = CreateLobbyInternal( lobbyType );
lobbyBackend->StartFinding( p, numPartyUsers, skillLevel );
return lobbyBackend;
}
/*
========================
idSessionLocalWin::JoinFromConnectInfo
========================
*/
idLobbyBackend * idSessionLocalWin::JoinFromConnectInfo( const lobbyConnectInfo_t & connectInfo, idLobbyBackend::lobbyBackendType_t lobbyType ) {
idLobbyBackend * lobbyBackend = CreateLobbyInternal( lobbyType );
lobbyBackend->JoinFromConnectInfo( connectInfo );
return lobbyBackend;
}
/*
========================
idSessionLocalWin::DestroyLobbyBackend
========================
*/
void idSessionLocalWin::DestroyLobbyBackend( idLobbyBackend * lobbyBackend ) {
assert( lobbyBackend != NULL );
assert( lobbyBackends[lobbyBackend->GetLobbyType()] == lobbyBackend );
lobbyBackends[lobbyBackend->GetLobbyType()] = NULL;
lobbyBackend->Shutdown();
delete lobbyBackend;
}
/*
========================
idSessionLocalWin::PumpLobbies
========================
*/
void idSessionLocalWin::PumpLobbies() {
assert( lobbyBackends[idLobbyBackend::TYPE_PARTY] == NULL || lobbyBackends[idLobbyBackend::TYPE_PARTY]->GetLobbyType() == idLobbyBackend::TYPE_PARTY );
assert( lobbyBackends[idLobbyBackend::TYPE_GAME] == NULL || lobbyBackends[idLobbyBackend::TYPE_GAME]->GetLobbyType() == idLobbyBackend::TYPE_GAME );
assert( lobbyBackends[idLobbyBackend::TYPE_GAME_STATE] == NULL || lobbyBackends[idLobbyBackend::TYPE_GAME_STATE]->GetLobbyType() == idLobbyBackend::TYPE_GAME_STATE );
// Pump lobbyBackends
for ( int i = 0; i < lobbyBackends.Num(); i++ ) {
if ( lobbyBackends[i] != NULL ) {
lobbyBackends[i]->Pump();
}
}
}
/*
========================
idSessionLocalWin::CreateLobbyInternal
========================
*/
idLobbyBackend * idSessionLocalWin::CreateLobbyInternal( idLobbyBackend::lobbyBackendType_t lobbyType ) {
EnsurePort();
idLobbyBackend * lobbyBackend = new (TAG_NETWORKING) idLobbyBackendDirect();
lobbyBackend->SetLobbyType( lobbyType );
assert( lobbyBackends[lobbyType] == NULL );
lobbyBackends[lobbyType] = lobbyBackend;
return lobbyBackend;
}
/*
========================
idSessionLocalWin::JoinAfterSwap
========================
*/
void idSessionLocalWin::JoinAfterSwap( void * joinID ) {
}
/*
========================
idSessionLocalWin::GetLobbyAddressFromNetAddress
========================
*/
bool idSessionLocalWin::GetLobbyAddressFromNetAddress( const netadr_t & netAddr, lobbyAddress_t & outAddr ) const {
return false;
}
/*
========================
idSessionLocalWin::GetNetAddressFromLobbyAddress
========================
*/
bool idSessionLocalWin::GetNetAddressFromLobbyAddress( const lobbyAddress_t & lobbyAddress, netadr_t & outNetAddr ) const {
return false;
}
+800
View File
@@ -0,0 +1,800 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
#include "win_local.h"
#include <lmerr.h>
#include <lmcons.h>
#include <lmwksta.h>
#include <errno.h>
#include <fcntl.h>
#include <direct.h>
#include <io.h>
#include <conio.h>
#undef StrCmpN
#undef StrCmpNI
#undef StrCmpI
#include <atlbase.h>
#include <comdef.h>
#include <comutil.h>
#include <Wbemidl.h>
#pragma comment (lib, "wbemuuid.lib")
#pragma warning(disable:4740) // warning C4740: flow in or out of inline asm code suppresses global optimization
/*
================
Sys_Milliseconds
================
*/
int Sys_Milliseconds() {
static DWORD sys_timeBase = timeGetTime();
return timeGetTime() - sys_timeBase;
}
/*
========================
Sys_Microseconds
========================
*/
uint64 Sys_Microseconds() {
static uint64 ticksPerMicrosecondTimes1024 = 0;
if ( ticksPerMicrosecondTimes1024 == 0 ) {
ticksPerMicrosecondTimes1024 = ( (uint64)Sys_ClockTicksPerSecond() << 10 ) / 1000000;
assert( ticksPerMicrosecondTimes1024 > 0 );
}
return ((uint64)( (int64)Sys_GetClockTicks() << 10 )) / ticksPerMicrosecondTimes1024;
}
/*
================
Sys_GetSystemRam
returns amount of physical memory in MB
================
*/
int Sys_GetSystemRam() {
MEMORYSTATUSEX statex;
statex.dwLength = sizeof ( statex );
GlobalMemoryStatusEx (&statex);
int physRam = statex.ullTotalPhys / ( 1024 * 1024 );
// HACK: For some reason, ullTotalPhys is sometimes off by a meg or two, so we round up to the nearest 16 megs
physRam = ( physRam + 8 ) & ~15;
return physRam;
}
/*
================
Sys_GetDriveFreeSpace
returns in megabytes
================
*/
int Sys_GetDriveFreeSpace( const char *path ) {
DWORDLONG lpFreeBytesAvailable;
DWORDLONG lpTotalNumberOfBytes;
DWORDLONG lpTotalNumberOfFreeBytes;
int ret = 26;
//FIXME: see why this is failing on some machines
if ( ::GetDiskFreeSpaceEx( path, (PULARGE_INTEGER)&lpFreeBytesAvailable, (PULARGE_INTEGER)&lpTotalNumberOfBytes, (PULARGE_INTEGER)&lpTotalNumberOfFreeBytes ) ) {
ret = ( double )( lpFreeBytesAvailable ) / ( 1024.0 * 1024.0 );
}
return ret;
}
/*
========================
Sys_GetDriveFreeSpaceInBytes
========================
*/
int64 Sys_GetDriveFreeSpaceInBytes( const char * path ) {
DWORDLONG lpFreeBytesAvailable;
DWORDLONG lpTotalNumberOfBytes;
DWORDLONG lpTotalNumberOfFreeBytes;
int64 ret = 1;
//FIXME: see why this is failing on some machines
if ( ::GetDiskFreeSpaceEx( path, (PULARGE_INTEGER)&lpFreeBytesAvailable, (PULARGE_INTEGER)&lpTotalNumberOfBytes, (PULARGE_INTEGER)&lpTotalNumberOfFreeBytes ) ) {
ret = lpFreeBytesAvailable;
}
return ret;
}
/*
================
Sys_GetVideoRam
returns in megabytes
================
*/
int Sys_GetVideoRam() {
unsigned int retSize = 64;
CComPtr<IWbemLocator> spLoc = NULL;
HRESULT hr = CoCreateInstance( CLSID_WbemLocator, 0, CLSCTX_SERVER, IID_IWbemLocator, ( LPVOID * ) &spLoc );
if ( hr != S_OK || spLoc == NULL ) {
return retSize;
}
CComBSTR bstrNamespace( _T( "\\\\.\\root\\CIMV2" ) );
CComPtr<IWbemServices> spServices;
// Connect to CIM
hr = spLoc->ConnectServer( bstrNamespace, NULL, NULL, 0, NULL, 0, 0, &spServices );
if ( hr != WBEM_S_NO_ERROR ) {
return retSize;
}
// Switch the security level to IMPERSONATE so that provider will grant access to system-level objects.
hr = CoSetProxyBlanket( spServices, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, NULL, RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE );
if ( hr != S_OK ) {
return retSize;
}
// Get the vid controller
CComPtr<IEnumWbemClassObject> spEnumInst = NULL;
hr = spServices->CreateInstanceEnum( CComBSTR( "Win32_VideoController" ), WBEM_FLAG_SHALLOW, NULL, &spEnumInst );
if ( hr != WBEM_S_NO_ERROR || spEnumInst == NULL ) {
return retSize;
}
ULONG uNumOfInstances = 0;
CComPtr<IWbemClassObject> spInstance = NULL;
hr = spEnumInst->Next( 10000, 1, &spInstance, &uNumOfInstances );
if ( hr == S_OK && spInstance ) {
// Get properties from the object
CComVariant varSize;
hr = spInstance->Get( CComBSTR( _T( "AdapterRAM" ) ), 0, &varSize, 0, 0 );
if ( hr == S_OK ) {
retSize = varSize.intVal / ( 1024 * 1024 );
if ( retSize == 0 ) {
retSize = 64;
}
}
}
return retSize;
}
/*
================
Sys_GetCurrentMemoryStatus
returns OS mem info
all values are in kB except the memoryload
================
*/
void Sys_GetCurrentMemoryStatus( sysMemoryStats_t &stats ) {
MEMORYSTATUSEX statex = {};
unsigned __int64 work;
statex.dwLength = sizeof( statex );
GlobalMemoryStatusEx( &statex );
memset( &stats, 0, sizeof( stats ) );
stats.memoryLoad = statex.dwMemoryLoad;
work = statex.ullTotalPhys >> 20;
stats.totalPhysical = *(int*)&work;
work = statex.ullAvailPhys >> 20;
stats.availPhysical = *(int*)&work;
work = statex.ullAvailPageFile >> 20;
stats.availPageFile = *(int*)&work;
work = statex.ullTotalPageFile >> 20;
stats.totalPageFile = *(int*)&work;
work = statex.ullTotalVirtual >> 20;
stats.totalVirtual = *(int*)&work;
work = statex.ullAvailVirtual >> 20;
stats.availVirtual = *(int*)&work;
work = statex.ullAvailExtendedVirtual >> 20;
stats.availExtendedVirtual = *(int*)&work;
}
/*
================
Sys_LockMemory
================
*/
bool Sys_LockMemory( void *ptr, int bytes ) {
return ( VirtualLock( ptr, (SIZE_T)bytes ) != FALSE );
}
/*
================
Sys_UnlockMemory
================
*/
bool Sys_UnlockMemory( void *ptr, int bytes ) {
return ( VirtualUnlock( ptr, (SIZE_T)bytes ) != FALSE );
}
/*
================
Sys_SetPhysicalWorkMemory
================
*/
void Sys_SetPhysicalWorkMemory( int minBytes, int maxBytes ) {
::SetProcessWorkingSetSize( GetCurrentProcess(), minBytes, maxBytes );
}
/*
================
Sys_GetCurrentUser
================
*/
char *Sys_GetCurrentUser() {
static char s_userName[1024];
unsigned long size = sizeof( s_userName );
if ( !GetUserName( s_userName, &size ) ) {
strcpy( s_userName, "player" );
}
if ( !s_userName[0] ) {
strcpy( s_userName, "player" );
}
return s_userName;
}
/*
===============================================================================
Call stack
===============================================================================
*/
#define PROLOGUE_SIGNATURE 0x00EC8B55
#include <dbghelp.h>
const int UNDECORATE_FLAGS = UNDNAME_NO_MS_KEYWORDS |
UNDNAME_NO_ACCESS_SPECIFIERS |
UNDNAME_NO_FUNCTION_RETURNS |
UNDNAME_NO_ALLOCATION_MODEL |
UNDNAME_NO_ALLOCATION_LANGUAGE |
UNDNAME_NO_MEMBER_TYPE;
#if defined(_DEBUG) && 1
typedef struct symbol_s {
int address;
char * name;
struct symbol_s * next;
} symbol_t;
typedef struct module_s {
int address;
char * name;
symbol_t * symbols;
struct module_s * next;
} module_t;
module_t *modules;
/*
==================
SkipRestOfLine
==================
*/
void SkipRestOfLine( const char **ptr ) {
while( (**ptr) != '\0' && (**ptr) != '\n' && (**ptr) != '\r' ) {
(*ptr)++;
}
while( (**ptr) == '\n' || (**ptr) == '\r' ) {
(*ptr)++;
}
}
/*
==================
SkipWhiteSpace
==================
*/
void SkipWhiteSpace( const char **ptr ) {
while( (**ptr) == ' ' ) {
(*ptr)++;
}
}
/*
==================
ParseHexNumber
==================
*/
int ParseHexNumber( const char **ptr ) {
int n = 0;
while( (**ptr) >= '0' && (**ptr) <= '9' || (**ptr) >= 'a' && (**ptr) <= 'f' ) {
n <<= 4;
if ( **ptr >= '0' && **ptr <= '9' ) {
n |= ( (**ptr) - '0' );
} else {
n |= 10 + ( (**ptr) - 'a' );
}
(*ptr)++;
}
return n;
}
/*
==================
Sym_Init
==================
*/
void Sym_Init( long addr ) {
TCHAR moduleName[MAX_STRING_CHARS];
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery( (void*)addr, &mbi, sizeof(mbi) );
GetModuleFileName( (HMODULE)mbi.AllocationBase, moduleName, sizeof( moduleName ) );
char *ext = moduleName + strlen( moduleName );
while( ext > moduleName && *ext != '.' ) {
ext--;
}
if ( ext == moduleName ) {
strcat( moduleName, ".map" );
} else {
strcpy( ext, ".map" );
}
module_t *module = (module_t *) malloc( sizeof( module_t ) );
module->name = (char *) malloc( strlen( moduleName ) + 1 );
strcpy( module->name, moduleName );
module->address = (int)mbi.AllocationBase;
module->symbols = NULL;
module->next = modules;
modules = module;
FILE * fp = fopen( moduleName, "rb" );
if ( fp == NULL ) {
return;
}
int pos = ftell( fp );
fseek( fp, 0, SEEK_END );
int length = ftell( fp );
fseek( fp, pos, SEEK_SET );
char *text = (char *) malloc( length+1 );
fread( text, 1, length, fp );
text[length] = '\0';
fclose( fp );
const char *ptr = text;
// skip up to " Address" on a new line
while( *ptr != '\0' ) {
SkipWhiteSpace( &ptr );
if ( idStr::Cmpn( ptr, "Address", 7 ) == 0 ) {
SkipRestOfLine( &ptr );
break;
}
SkipRestOfLine( &ptr );
}
int symbolAddress;
int symbolLength;
char symbolName[MAX_STRING_CHARS];
symbol_t *symbol;
// parse symbols
while( *ptr != '\0' ) {
SkipWhiteSpace( &ptr );
ParseHexNumber( &ptr );
if ( *ptr == ':' ) {
ptr++;
} else {
break;
}
ParseHexNumber( &ptr );
SkipWhiteSpace( &ptr );
// parse symbol name
symbolLength = 0;
while( *ptr != '\0' && *ptr != ' ' ) {
symbolName[symbolLength++] = *ptr++;
if ( symbolLength >= sizeof( symbolName ) - 1 ) {
break;
}
}
symbolName[symbolLength++] = '\0';
SkipWhiteSpace( &ptr );
// parse symbol address
symbolAddress = ParseHexNumber( &ptr );
SkipRestOfLine( &ptr );
symbol = (symbol_t *) malloc( sizeof( symbol_t ) );
symbol->name = (char *) malloc( symbolLength );
strcpy( symbol->name, symbolName );
symbol->address = symbolAddress;
symbol->next = module->symbols;
module->symbols = symbol;
}
free( text );
}
/*
==================
Sym_Shutdown
==================
*/
void Sym_Shutdown() {
module_t *m;
symbol_t *s;
for ( m = modules; m != NULL; m = modules ) {
modules = m->next;
for ( s = m->symbols; s != NULL; s = m->symbols ) {
m->symbols = s->next;
free( s->name );
free( s );
}
free( m->name );
free( m );
}
modules = NULL;
}
/*
==================
Sym_GetFuncInfo
==================
*/
void Sym_GetFuncInfo( long addr, idStr &module, idStr &funcName ) {
MEMORY_BASIC_INFORMATION mbi;
module_t *m;
symbol_t *s;
VirtualQuery( (void*)addr, &mbi, sizeof(mbi) );
for ( m = modules; m != NULL; m = m->next ) {
if ( m->address == (int) mbi.AllocationBase ) {
break;
}
}
if ( !m ) {
Sym_Init( addr );
m = modules;
}
for ( s = m->symbols; s != NULL; s = s->next ) {
if ( s->address == addr ) {
char undName[MAX_STRING_CHARS];
if ( UnDecorateSymbolName( s->name, undName, sizeof(undName), UNDECORATE_FLAGS ) ) {
funcName = undName;
} else {
funcName = s->name;
}
for ( int i = 0; i < funcName.Length(); i++ ) {
if ( funcName[i] == '(' ) {
funcName.CapLength( i );
break;
}
}
module = m->name;
return;
}
}
sprintf( funcName, "0x%08x", addr );
module = "";
}
#elif defined(_DEBUG)
DWORD lastAllocationBase = -1;
HANDLE processHandle;
idStr lastModule;
/*
==================
Sym_Init
==================
*/
void Sym_Init( long addr ) {
TCHAR moduleName[MAX_STRING_CHARS];
TCHAR modShortNameBuf[MAX_STRING_CHARS];
MEMORY_BASIC_INFORMATION mbi;
if ( lastAllocationBase != -1 ) {
Sym_Shutdown();
}
VirtualQuery( (void*)addr, &mbi, sizeof(mbi) );
GetModuleFileName( (HMODULE)mbi.AllocationBase, moduleName, sizeof( moduleName ) );
_splitpath( moduleName, NULL, NULL, modShortNameBuf, NULL );
lastModule = modShortNameBuf;
processHandle = GetCurrentProcess();
if ( !SymInitialize( processHandle, NULL, FALSE ) ) {
return;
}
if ( !SymLoadModule( processHandle, NULL, moduleName, NULL, (DWORD)mbi.AllocationBase, 0 ) ) {
SymCleanup( processHandle );
return;
}
SymSetOptions( SymGetOptions() & ~SYMOPT_UNDNAME );
lastAllocationBase = (DWORD) mbi.AllocationBase;
}
/*
==================
Sym_Shutdown
==================
*/
void Sym_Shutdown() {
SymUnloadModule( GetCurrentProcess(), lastAllocationBase );
SymCleanup( GetCurrentProcess() );
lastAllocationBase = -1;
}
/*
==================
Sym_GetFuncInfo
==================
*/
void Sym_GetFuncInfo( long addr, idStr &module, idStr &funcName ) {
MEMORY_BASIC_INFORMATION mbi;
VirtualQuery( (void*)addr, &mbi, sizeof(mbi) );
if ( (DWORD) mbi.AllocationBase != lastAllocationBase ) {
Sym_Init( addr );
}
BYTE symbolBuffer[ sizeof(IMAGEHLP_SYMBOL) + MAX_STRING_CHARS ];
PIMAGEHLP_SYMBOL pSymbol = (PIMAGEHLP_SYMBOL)&symbolBuffer[0];
pSymbol->SizeOfStruct = sizeof(symbolBuffer);
pSymbol->MaxNameLength = 1023;
pSymbol->Address = 0;
pSymbol->Flags = 0;
pSymbol->Size =0;
DWORD symDisplacement = 0;
if ( SymGetSymFromAddr( processHandle, addr, &symDisplacement, pSymbol ) ) {
// clean up name, throwing away decorations that don't affect uniqueness
char undName[MAX_STRING_CHARS];
if ( UnDecorateSymbolName( pSymbol->Name, undName, sizeof(undName), UNDECORATE_FLAGS ) ) {
funcName = undName;
} else {
funcName = pSymbol->Name;
}
module = lastModule;
}
else {
LPVOID lpMsgBuf;
FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
GetLastError(),
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
(LPTSTR) &lpMsgBuf,
0,
NULL
);
LocalFree( lpMsgBuf );
// Couldn't retrieve symbol (no debug info?, can't load dbghelp.dll?)
sprintf( funcName, "0x%08x", addr );
module = "";
}
}
#else
/*
==================
Sym_Init
==================
*/
void Sym_Init( long addr ) {
}
/*
==================
Sym_Shutdown
==================
*/
void Sym_Shutdown() {
}
/*
==================
Sym_GetFuncInfo
==================
*/
void Sym_GetFuncInfo( long addr, idStr &module, idStr &funcName ) {
module = "";
sprintf( funcName, "0x%08x", addr );
}
#endif
/*
==================
GetFuncAddr
==================
*/
address_t GetFuncAddr( address_t midPtPtr ) {
long temp;
do {
temp = (long)(*(long*)midPtPtr);
if ( (temp&0x00FFFFFF) == PROLOGUE_SIGNATURE ) {
break;
}
midPtPtr--;
} while(true);
return midPtPtr;
}
/*
==================
GetCallerAddr
==================
*/
address_t GetCallerAddr( long _ebp ) {
long midPtPtr;
long res = 0;
__asm {
mov eax, _ebp
mov ecx, [eax] // check for end of stack frames list
test ecx, ecx // check for zero stack frame
jz label
mov eax, [eax+4] // get the ret address
test eax, eax // check for zero return address
jz label
mov midPtPtr, eax
}
res = GetFuncAddr( midPtPtr );
label:
return res;
}
/*
==================
Sys_GetCallStack
use /Oy option
==================
*/
void Sys_GetCallStack( address_t *callStack, const int callStackSize ) {
#if 1 //def _DEBUG
int i;
long m_ebp;
__asm {
mov eax, ebp
mov m_ebp, eax
}
// skip last two functions
m_ebp = *((long*)m_ebp);
m_ebp = *((long*)m_ebp);
// list functions
for ( i = 0; i < callStackSize; i++ ) {
callStack[i] = GetCallerAddr( m_ebp );
if ( callStack[i] == 0 ) {
break;
}
m_ebp = *((long*)m_ebp);
}
#else
int i = 0;
#endif
while( i < callStackSize ) {
callStack[i++] = 0;
}
}
/*
==================
Sys_GetCallStackStr
==================
*/
const char *Sys_GetCallStackStr( const address_t *callStack, const int callStackSize ) {
static char string[MAX_STRING_CHARS*2];
int index, i;
idStr module, funcName;
index = 0;
for ( i = callStackSize-1; i >= 0; i-- ) {
Sym_GetFuncInfo( callStack[i], module, funcName );
index += sprintf( string+index, " -> %s", funcName.c_str() );
}
return string;
}
/*
==================
Sys_GetCallStackCurStr
==================
*/
const char *Sys_GetCallStackCurStr( int depth ) {
address_t *callStack;
callStack = (address_t *) _alloca( depth * sizeof( address_t ) );
Sys_GetCallStack( callStack, depth );
return Sys_GetCallStackStr( callStack, depth );
}
/*
==================
Sys_GetCallStackCurAddressStr
==================
*/
const char *Sys_GetCallStackCurAddressStr( int depth ) {
static char string[MAX_STRING_CHARS*2];
address_t *callStack;
int index, i;
callStack = (address_t *) _alloca( depth * sizeof( address_t ) );
Sys_GetCallStack( callStack, depth );
index = 0;
for ( i = depth-1; i >= 0; i-- ) {
index += sprintf( string+index, " -> 0x%08x", callStack[i] );
}
return string;
}
/*
==================
Sys_ShutdownSymbols
==================
*/
void Sys_ShutdownSymbols() {
Sym_Shutdown();
}
+146
View File
@@ -0,0 +1,146 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
#include "../../framework/PlayerProfile.h"
#include "../sys_session_local.h"
#include "win_signin.h"
#ifdef _DEBUG
idCVar win_userPersistent( "win_userPersistent", "1", CVAR_BOOL, "debugging cvar for profile persistence status" );
idCVar win_userOnline( "win_userOnline", "1", CVAR_BOOL, "debugging cvar for profile online status" );
idCVar win_isInParty( "win_isInParty", "0", CVAR_BOOL, "debugging cvar for platform party status" );
idCVar win_partyCount( "win_partyCount", "0", CVAR_INTEGER, "debugginc var for platform party count" );
#endif
/*
========================
idSignInManagerWin::Shutdown
========================
*/
void idSignInManagerWin::Shutdown() {
}
/*
========================
idSignInManagerWin::Pump
========================
*/
void idSignInManagerWin::Pump() {
// If we have more users than we need, then set to the lower amount
// (don't remove the master user though)
if ( localUsers.Num() > 1 && localUsers.Num() > maxDesiredLocalUsers ) {
localUsers.SetNum( maxDesiredLocalUsers );
}
#ifndef ID_RETAIL
// If we don't have enough, then make sure we do
// NOTE - We always want at least one user on windows for now,
// and this master user will always use controller 0
while ( localUsers.Num() < minDesiredLocalUsers ) {
RegisterLocalUser( localUsers.Num() );
}
#endif
// See if we need to save settings on any of the profiles
for ( int i = 0; i < localUsers.Num(); i++ ) {
localUsers[i].Pump();
}
}
/*
========================
idSignInManagerWin::RemoveLocalUserByIndex
========================
*/
void idSignInManagerWin::RemoveLocalUserByIndex( int index ) {
session->OnLocalUserSignout( &localUsers[index] );
localUsers.RemoveIndex( index );
}
/*
========================
idSignInManagerWin::RegisterLocalUser
========================
*/
void idSignInManagerWin::RegisterLocalUser( int inputDevice ) {
if ( GetLocalUserByInputDevice( inputDevice ) != NULL ) {
return;
}
static char machineName[128];
DWORD len = 128;
::GetComputerName( machineName, &len );
const char * nameSource = machineName;
idStr name( nameSource );
int nameLength = name.Length();
if ( idStr::IsValidUTF8( nameSource, nameLength ) ) {
int nameIndex = 0;
int numChars = 0;
name.Empty();
while ( nameIndex < nameLength && numChars++ < idLocalUserWin::MAX_GAMERTAG_CHARS ) {
uint32 c = idStr::UTF8Char( nameSource, nameIndex );
name.AppendUTF8Char( c );
}
}
idLocalUserWin & localUser = *localUsers.Alloc();
localUser.Init( inputDevice, name.c_str(), localUsers.Num() );
localUser.SetLocalUserHandle( GetUniqueLocalUserHandle( localUser.GetGamerTag() ) );
session->OnLocalUserSignin( &localUser );
}
/*
========================
idSignInManagerWin::CreateNewUser
========================
*/
bool idSignInManagerWin::CreateNewUser( winUserState_t & state ) {
//idScopedGlobalHeap everythingHereGoesInTheGlobalHeap; // users obviously persist across maps
RemoveAllLocalUsers();
RegisterLocalUser( state.inputDevice );
if ( localUsers.Num() > 0 ) {
if ( !localUsers[0].VerifyUserState( state ) ) {
RemoveAllLocalUsers();
}
}
return true;
}
CONSOLE_COMMAND( testRemoveAllLocalUsers, "Forces removal of local users - mainly for PC testing", NULL ) {
session->GetSignInManager().RemoveAllLocalUsers();
}
+62
View File
@@ -0,0 +1,62 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __WIN_SIGNIN_H__
#define __WIN_SIGNIN_H__
#include "win_localuser.h"
/*
================================================
idSignInManagerWin
================================================
*/
class idSignInManagerWin : public idSignInManagerBase {
public:
idSignInManagerWin() : dlcVersionChecked( false ) {}
virtual ~idSignInManagerWin() {}
//==========================================================================================
// idSignInManagerBase interface
//==========================================================================================
virtual void Pump();
virtual void Shutdown();
virtual int GetNumLocalUsers() const { return localUsers.Num(); }
virtual idLocalUser * GetLocalUserByIndex( int index ) { return &localUsers[index]; }
virtual const idLocalUser * GetLocalUserByIndex( int index ) const { return &localUsers[index]; }
virtual void RemoveLocalUserByIndex( int index );
virtual void RegisterLocalUser( int inputDevice ); // Register a local user to the passed in controller
bool CreateNewUser( winUserState_t & state );
private:
idStaticList< idLocalUserWin, MAX_INPUT_DEVICES > localUsers;
bool dlcVersionChecked;
};
#endif
+37
View File
@@ -0,0 +1,37 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
// DirectX SDK
#include <DxErr.h>
#include <ks.h>
#include <ksmedia.h>
#include "../../sound/snd_local.h"
#include "win_local.h"
+29
View File
@@ -0,0 +1,29 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../framework/precompiled.h"
+32
View File
@@ -0,0 +1,32 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#ifndef __WIN_STATS_H__
#define __WIN_STATS_H__
#endif // !__WIN_STATS_H__
+553
View File
@@ -0,0 +1,553 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
#include <errno.h>
#include <float.h>
#include <fcntl.h>
#include <stdio.h>
#include <direct.h>
#include <io.h>
#include <conio.h>
#include "win_local.h"
#include "rc/doom_resource.h"
#define COPY_ID 1
#define QUIT_ID 2
#define CLEAR_ID 3
#define ERRORBOX_ID 10
#define ERRORTEXT_ID 11
#define EDIT_ID 100
#define INPUT_ID 101
#define COMMAND_HISTORY 64
typedef struct {
HWND hWnd;
HWND hwndBuffer;
HWND hwndButtonClear;
HWND hwndButtonCopy;
HWND hwndButtonQuit;
HWND hwndErrorBox;
HWND hwndErrorText;
HBITMAP hbmLogo;
HBITMAP hbmClearBitmap;
HBRUSH hbrEditBackground;
HBRUSH hbrErrorBackground;
HFONT hfBufferFont;
HFONT hfButtonFont;
HWND hwndInputLine;
char errorString[80];
char consoleText[512], returnedText[512];
bool quitOnClose;
int windowWidth, windowHeight;
WNDPROC SysInputLineWndProc;
idEditField historyEditLines[COMMAND_HISTORY];
int nextHistoryLine;// the last line in the history buffer, not masked
int historyLine; // the line being displayed from history buffer
// will be <= nextHistoryLine
idEditField consoleField;
} WinConData;
static WinConData s_wcd;
static LONG WINAPI ConWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
char *cmdString;
static bool s_timePolarity;
switch (uMsg) {
case WM_ACTIVATE:
if ( LOWORD( wParam ) != WA_INACTIVE ) {
SetFocus( s_wcd.hwndInputLine );
}
break;
case WM_CLOSE:
if ( s_wcd.quitOnClose ) {
PostQuitMessage( 0 );
} else {
Sys_ShowConsole( 0, false );
win32.win_viewlog.SetBool( false );
}
return 0;
case WM_CTLCOLORSTATIC:
if ( ( HWND ) lParam == s_wcd.hwndBuffer ) {
SetBkColor( ( HDC ) wParam, RGB( 0x00, 0x00, 0x80 ) );
SetTextColor( ( HDC ) wParam, RGB( 0xff, 0xff, 0x00 ) );
return ( long ) s_wcd.hbrEditBackground;
} else if ( ( HWND ) lParam == s_wcd.hwndErrorBox ) {
if ( s_timePolarity & 1 ) {
SetBkColor( ( HDC ) wParam, RGB( 0x80, 0x80, 0x80 ) );
SetTextColor( ( HDC ) wParam, RGB( 0xff, 0x0, 0x00 ) );
} else {
SetBkColor( ( HDC ) wParam, RGB( 0x80, 0x80, 0x80 ) );
SetTextColor( ( HDC ) wParam, RGB( 0x00, 0x0, 0x00 ) );
}
return ( long ) s_wcd.hbrErrorBackground;
}
break;
case WM_SYSCOMMAND:
if ( wParam == SC_CLOSE ) {
PostQuitMessage( 0 );
}
break;
case WM_COMMAND:
if ( wParam == COPY_ID ) {
SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 );
SendMessage( s_wcd.hwndBuffer, WM_COPY, 0, 0 );
} else if ( wParam == QUIT_ID ) {
if ( s_wcd.quitOnClose ) {
PostQuitMessage( 0 );
} else {
cmdString = Mem_CopyString( "quit" );
Sys_QueEvent( SE_CONSOLE, 0, 0, strlen( cmdString ) + 1, cmdString, 0 );
}
} else if ( wParam == CLEAR_ID ) {
SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 );
SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, FALSE, ( LPARAM ) "" );
UpdateWindow( s_wcd.hwndBuffer );
}
break;
case WM_CREATE:
s_wcd.hbrEditBackground = CreateSolidBrush( RGB( 0x00, 0x00, 0x80 ) );
s_wcd.hbrErrorBackground = CreateSolidBrush( RGB( 0x80, 0x80, 0x80 ) );
SetTimer( hWnd, 1, 1000, NULL );
break;
/*
case WM_ERASEBKGND:
HGDIOBJ oldObject;
HDC hdcScaled;
hdcScaled = CreateCompatibleDC( ( HDC ) wParam );
assert( hdcScaled != 0 );
if ( hdcScaled ) {
oldObject = SelectObject( ( HDC ) hdcScaled, s_wcd.hbmLogo );
assert( oldObject != 0 );
if ( oldObject )
{
StretchBlt( ( HDC ) wParam, 0, 0, s_wcd.windowWidth, s_wcd.windowHeight,
hdcScaled, 0, 0, 512, 384,
SRCCOPY );
}
DeleteDC( hdcScaled );
hdcScaled = 0;
}
return 1;
*/
case WM_TIMER:
if ( wParam == 1 ) {
s_timePolarity = (bool)!s_timePolarity;
if ( s_wcd.hwndErrorBox ) {
InvalidateRect( s_wcd.hwndErrorBox, NULL, FALSE );
}
}
break;
}
return DefWindowProc( hWnd, uMsg, wParam, lParam );
}
LONG WINAPI InputLineWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
int key, cursor;
switch ( uMsg ) {
case WM_KILLFOCUS:
if ( ( HWND ) wParam == s_wcd.hWnd || ( HWND ) wParam == s_wcd.hwndErrorBox ) {
SetFocus( hWnd );
return 0;
}
break;
case WM_KEYDOWN:
key = ( ( lParam >> 16 ) & 0xFF ) | ( ( ( lParam >> 24 ) & 1 ) << 7 );
// command history
if ( ( key == K_UPARROW ) || ( key == K_KP_8 ) ) {
if ( s_wcd.nextHistoryLine - s_wcd.historyLine < COMMAND_HISTORY && s_wcd.historyLine > 0 ) {
s_wcd.historyLine--;
}
s_wcd.consoleField = s_wcd.historyEditLines[ s_wcd.historyLine % COMMAND_HISTORY ];
SetWindowText( s_wcd.hwndInputLine, s_wcd.consoleField.GetBuffer() );
SendMessage( s_wcd.hwndInputLine, EM_SETSEL, s_wcd.consoleField.GetCursor(), s_wcd.consoleField.GetCursor() );
return 0;
}
if ( ( key == K_DOWNARROW ) || ( key == K_KP_2 ) ) {
if ( s_wcd.historyLine == s_wcd.nextHistoryLine ) {
return 0;
}
s_wcd.historyLine++;
s_wcd.consoleField = s_wcd.historyEditLines[ s_wcd.historyLine % COMMAND_HISTORY ];
SetWindowText( s_wcd.hwndInputLine, s_wcd.consoleField.GetBuffer() );
SendMessage( s_wcd.hwndInputLine, EM_SETSEL, s_wcd.consoleField.GetCursor(), s_wcd.consoleField.GetCursor() );
return 0;
}
break;
case WM_CHAR:
key = ( ( lParam >> 16 ) & 0xFF ) | ( ( ( lParam >> 24 ) & 1 ) << 7 );
GetWindowText( s_wcd.hwndInputLine, s_wcd.consoleField.GetBuffer(), MAX_EDIT_LINE );
SendMessage( s_wcd.hwndInputLine, EM_GETSEL, (WPARAM) NULL, (LPARAM) &cursor );
s_wcd.consoleField.SetCursor( cursor );
// enter the line
if ( key == K_ENTER || key == K_KP_ENTER ) {
strncat( s_wcd.consoleText, s_wcd.consoleField.GetBuffer(), sizeof( s_wcd.consoleText ) - strlen( s_wcd.consoleText ) - 5 );
strcat( s_wcd.consoleText, "\n" );
SetWindowText( s_wcd.hwndInputLine, "" );
Sys_Printf( "]%s\n", s_wcd.consoleField.GetBuffer() );
// copy line to history buffer
s_wcd.historyEditLines[s_wcd.nextHistoryLine % COMMAND_HISTORY] = s_wcd.consoleField;
s_wcd.nextHistoryLine++;
s_wcd.historyLine = s_wcd.nextHistoryLine;
s_wcd.consoleField.Clear();
return 0;
}
// command completion
if ( key == K_TAB ) {
s_wcd.consoleField.AutoComplete();
SetWindowText( s_wcd.hwndInputLine, s_wcd.consoleField.GetBuffer() );
//s_wcd.consoleField.SetWidthInChars( strlen( s_wcd.consoleField.GetBuffer() ) );
SendMessage( s_wcd.hwndInputLine, EM_SETSEL, s_wcd.consoleField.GetCursor(), s_wcd.consoleField.GetCursor() );
return 0;
}
// clear autocompletion buffer on normal key input
if ( ( key >= K_SPACE && key <= K_BACKSPACE ) ||
( key >= K_KP_SLASH && key <= K_KP_PLUS ) || ( key >= K_KP_STAR && key <= K_KP_EQUALS ) ) {
s_wcd.consoleField.ClearAutoComplete();
}
break;
}
return CallWindowProc( s_wcd.SysInputLineWndProc, hWnd, uMsg, wParam, lParam );
}
/*
** Sys_CreateConsole
*/
void Sys_CreateConsole() {
HDC hDC;
WNDCLASS wc;
RECT rect;
const char *DEDCLASS = WIN32_CONSOLE_CLASS;
int nHeight;
int swidth, sheight;
int DEDSTYLE = WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX;
int i;
memset( &wc, 0, sizeof( wc ) );
wc.style = 0;
wc.lpfnWndProc = (WNDPROC) ConWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = win32.hInstance;
wc.hIcon = LoadIcon( win32.hInstance, MAKEINTRESOURCE(IDI_ICON1));
wc.hCursor = LoadCursor (NULL,IDC_ARROW);
wc.hbrBackground = (struct HBRUSH__ *)COLOR_WINDOW;
wc.lpszMenuName = 0;
wc.lpszClassName = DEDCLASS;
if ( !RegisterClass (&wc) ) {
return;
}
rect.left = 0;
rect.right = 540;
rect.top = 0;
rect.bottom = 450;
AdjustWindowRect( &rect, DEDSTYLE, FALSE );
hDC = GetDC( GetDesktopWindow() );
swidth = GetDeviceCaps( hDC, HORZRES );
sheight = GetDeviceCaps( hDC, VERTRES );
ReleaseDC( GetDesktopWindow(), hDC );
s_wcd.windowWidth = rect.right - rect.left + 1;
s_wcd.windowHeight = rect.bottom - rect.top + 1;
//s_wcd.hbmLogo = LoadBitmap( win32.hInstance, MAKEINTRESOURCE( IDB_BITMAP_LOGO) );
s_wcd.hWnd = CreateWindowEx( 0,
DEDCLASS,
GAME_NAME,
DEDSTYLE,
( swidth - 600 ) / 2, ( sheight - 450 ) / 2 , rect.right - rect.left + 1, rect.bottom - rect.top + 1,
NULL,
NULL,
win32.hInstance,
NULL );
if ( s_wcd.hWnd == NULL ) {
return;
}
//
// create fonts
//
hDC = GetDC( s_wcd.hWnd );
nHeight = -MulDiv( 8, GetDeviceCaps( hDC, LOGPIXELSY ), 72 );
s_wcd.hfBufferFont = CreateFont( nHeight, 0, 0, 0, FW_LIGHT, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, FF_MODERN | FIXED_PITCH, "Courier New" );
ReleaseDC( s_wcd.hWnd, hDC );
//
// create the input line
//
s_wcd.hwndInputLine = CreateWindow( "edit", NULL, WS_CHILD | WS_VISIBLE | WS_BORDER |
ES_LEFT | ES_AUTOHSCROLL,
6, 400, 528, 20,
s_wcd.hWnd,
( HMENU ) INPUT_ID, // child window ID
win32.hInstance, NULL );
//
// create the buttons
//
s_wcd.hwndButtonCopy = CreateWindow( "button", NULL, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
5, 425, 72, 24,
s_wcd.hWnd,
( HMENU ) COPY_ID, // child window ID
win32.hInstance, NULL );
SendMessage( s_wcd.hwndButtonCopy, WM_SETTEXT, 0, ( LPARAM ) "copy" );
s_wcd.hwndButtonClear = CreateWindow( "button", NULL, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
82, 425, 72, 24,
s_wcd.hWnd,
( HMENU ) CLEAR_ID, // child window ID
win32.hInstance, NULL );
SendMessage( s_wcd.hwndButtonClear, WM_SETTEXT, 0, ( LPARAM ) "clear" );
s_wcd.hwndButtonQuit = CreateWindow( "button", NULL, BS_PUSHBUTTON | WS_VISIBLE | WS_CHILD | BS_DEFPUSHBUTTON,
462, 425, 72, 24,
s_wcd.hWnd,
( HMENU ) QUIT_ID, // child window ID
win32.hInstance, NULL );
SendMessage( s_wcd.hwndButtonQuit, WM_SETTEXT, 0, ( LPARAM ) "quit" );
//
// create the scrollbuffer
//
s_wcd.hwndBuffer = CreateWindow( "edit", NULL, WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_BORDER |
ES_LEFT | ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY,
6, 40, 526, 354,
s_wcd.hWnd,
( HMENU ) EDIT_ID, // child window ID
win32.hInstance, NULL );
SendMessage( s_wcd.hwndBuffer, WM_SETFONT, ( WPARAM ) s_wcd.hfBufferFont, 0 );
s_wcd.SysInputLineWndProc = ( WNDPROC ) SetWindowLong( s_wcd.hwndInputLine, GWL_WNDPROC, ( long ) InputLineWndProc );
SendMessage( s_wcd.hwndInputLine, WM_SETFONT, ( WPARAM ) s_wcd.hfBufferFont, 0 );
// don't show it now that we have a splash screen up
if ( win32.win_viewlog.GetBool() ) {
ShowWindow( s_wcd.hWnd, SW_SHOWDEFAULT);
UpdateWindow( s_wcd.hWnd );
SetForegroundWindow( s_wcd.hWnd );
SetFocus( s_wcd.hwndInputLine );
}
s_wcd.consoleField.Clear();
for ( i = 0 ; i < COMMAND_HISTORY ; i++ ) {
s_wcd.historyEditLines[i].Clear();
}
}
/*
** Sys_DestroyConsole
*/
void Sys_DestroyConsole() {
if ( s_wcd.hWnd ) {
ShowWindow( s_wcd.hWnd, SW_HIDE );
CloseWindow( s_wcd.hWnd );
DestroyWindow( s_wcd.hWnd );
s_wcd.hWnd = 0;
}
}
/*
** Sys_ShowConsole
*/
void Sys_ShowConsole( int visLevel, bool quitOnClose ) {
s_wcd.quitOnClose = quitOnClose;
if ( !s_wcd.hWnd ) {
return;
}
switch ( visLevel ) {
case 0:
ShowWindow( s_wcd.hWnd, SW_HIDE );
break;
case 1:
ShowWindow( s_wcd.hWnd, SW_SHOWNORMAL );
SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff );
break;
case 2:
ShowWindow( s_wcd.hWnd, SW_MINIMIZE );
break;
default:
Sys_Error( "Invalid visLevel %d sent to Sys_ShowConsole\n", visLevel );
break;
}
}
/*
** Sys_ConsoleInput
*/
char *Sys_ConsoleInput() {
if ( s_wcd.consoleText[0] == 0 ) {
return NULL;
}
strcpy( s_wcd.returnedText, s_wcd.consoleText );
s_wcd.consoleText[0] = 0;
return s_wcd.returnedText;
}
/*
** Conbuf_AppendText
*/
void Conbuf_AppendText( const char *pMsg )
{
#define CONSOLE_BUFFER_SIZE 16384
char buffer[CONSOLE_BUFFER_SIZE*2];
char *b = buffer;
const char *msg;
int bufLen;
int i = 0;
static unsigned long s_totalChars;
//
// if the message is REALLY long, use just the last portion of it
//
if ( strlen( pMsg ) > CONSOLE_BUFFER_SIZE - 1 ) {
msg = pMsg + strlen( pMsg ) - CONSOLE_BUFFER_SIZE + 1;
} else {
msg = pMsg;
}
//
// copy into an intermediate buffer
//
while ( msg[i] && ( ( b - buffer ) < sizeof( buffer ) - 1 ) ) {
if ( msg[i] == '\n' && msg[i+1] == '\r' ) {
b[0] = '\r';
b[1] = '\n';
b += 2;
i++;
} else if ( msg[i] == '\r' ) {
b[0] = '\r';
b[1] = '\n';
b += 2;
} else if ( msg[i] == '\n' ) {
b[0] = '\r';
b[1] = '\n';
b += 2;
} else if ( idStr::IsColor( &msg[i] ) ) {
i++;
} else {
*b= msg[i];
b++;
}
i++;
}
*b = 0;
bufLen = b - buffer;
s_totalChars += bufLen;
//
// replace selection instead of appending if we're overflowing
//
if ( s_totalChars > 0x7000 ) {
SendMessage( s_wcd.hwndBuffer, EM_SETSEL, 0, -1 );
s_totalChars = bufLen;
}
//
// put this text into the windows console
//
SendMessage( s_wcd.hwndBuffer, EM_LINESCROLL, 0, 0xffff );
SendMessage( s_wcd.hwndBuffer, EM_SCROLLCARET, 0, 0 );
SendMessage( s_wcd.hwndBuffer, EM_REPLACESEL, 0, (LPARAM) buffer );
}
/*
** Win_SetErrorText
*/
void Win_SetErrorText( const char *buf ) {
idStr::Copynz( s_wcd.errorString, buf, sizeof( s_wcd.errorString ) );
if ( !s_wcd.hwndErrorBox ) {
s_wcd.hwndErrorBox = CreateWindow( "static", NULL, WS_CHILD | WS_VISIBLE | SS_SUNKEN,
6, 5, 526, 30,
s_wcd.hWnd,
( HMENU ) ERRORBOX_ID, // child window ID
win32.hInstance, NULL );
SendMessage( s_wcd.hwndErrorBox, WM_SETFONT, ( WPARAM ) s_wcd.hfBufferFont, 0 );
SetWindowText( s_wcd.hwndErrorBox, s_wcd.errorString );
DestroyWindow( s_wcd.hwndInputLine );
s_wcd.hwndInputLine = NULL;
}
}
+142
View File
@@ -0,0 +1,142 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
//
// This file implements the low-level keyboard hook that traps the task keys.
//
#include "win_local.h"
#define DLLEXPORT __declspec(dllexport)
// Magic registry key/value for "Remove Task Manager" policy.
LPCTSTR KEY_DisableTaskMgr = "Software\\Microsoft\\Windows\\CurrentVersion\\Policies\\System";
LPCTSTR VAL_DisableTaskMgr = "DisableTaskMgr";
// The section is SHARED among all instances of this DLL.
// A low-level keyboard hook is always a system-wide hook.
#pragma data_seg (".mydata")
HHOOK g_hHookKbdLL = NULL; // hook handle
BOOL g_bBeep = FALSE; // beep on illegal key
#pragma data_seg ()
#pragma comment(linker, "/SECTION:.mydata,RWS") // tell linker: make it shared
/*
================
MyTaskKeyHookLL
Low-level keyboard hook:
Trap task-switching keys by returning without passing along.
================
*/
LRESULT CALLBACK MyTaskKeyHookLL( int nCode, WPARAM wp, LPARAM lp ) {
KBDLLHOOKSTRUCT *pkh = (KBDLLHOOKSTRUCT *) lp;
if ( nCode == HC_ACTION ) {
BOOL bCtrlKeyDown = GetAsyncKeyState( VK_CONTROL)>>((sizeof(SHORT) * 8) - 1 );
if ( ( pkh->vkCode == VK_ESCAPE && bCtrlKeyDown ) // Ctrl+Esc
|| ( pkh->vkCode == VK_TAB && pkh->flags & LLKHF_ALTDOWN ) // Alt+TAB
|| ( pkh->vkCode == VK_ESCAPE && pkh->flags & LLKHF_ALTDOWN ) // Alt+Esc
|| ( pkh->vkCode == VK_LWIN || pkh->vkCode == VK_RWIN ) // Start Menu
) {
if ( g_bBeep && ( wp == WM_SYSKEYDOWN || wp == WM_KEYDOWN ) ) {
MessageBeep( 0 ); // beep on downstroke if requested
}
return 1; // return without processing the key strokes
}
}
return CallNextHookEx( g_hHookKbdLL, nCode, wp, lp );
}
/*
================
AreTaskKeysDisabled
Are task keys disabled--ie, is hook installed?
Note: This assumes there's no other hook that does the same thing!
================
*/
BOOL AreTaskKeysDisabled() {
return g_hHookKbdLL != NULL;
}
/*
================
IsTaskMgrDisabled
================
*/
BOOL IsTaskMgrDisabled() {
HKEY hk;
if ( RegOpenKey( HKEY_CURRENT_USER, KEY_DisableTaskMgr, &hk ) != ERROR_SUCCESS ) {
return FALSE; // no key ==> not disabled
}
DWORD val = 0;
DWORD len = 4;
return RegQueryValueEx( hk, VAL_DisableTaskMgr, NULL, NULL, (BYTE*)&val, &len ) == ERROR_SUCCESS && val == 1;
}
/*
================
DisableTaskKeys
================
*/
void DisableTaskKeys( BOOL bDisable, BOOL bBeep, BOOL bTaskMgr ) {
// task keys (Ctrl+Esc, Alt-Tab, etc.)
if ( bDisable ) {
if ( !g_hHookKbdLL ) {
g_hHookKbdLL = SetWindowsHookEx( WH_KEYBOARD_LL, MyTaskKeyHookLL, win32.hInstance, 0 );
}
} else if ( g_hHookKbdLL != NULL ) {
UnhookWindowsHookEx( g_hHookKbdLL );
g_hHookKbdLL = NULL;
}
g_bBeep = bBeep;
// task manager (Ctrl+Alt+Del)
if ( bTaskMgr ) {
HKEY hk;
if ( RegOpenKey( HKEY_CURRENT_USER, KEY_DisableTaskMgr, &hk ) != ERROR_SUCCESS ) {
RegCreateKey( HKEY_CURRENT_USER, KEY_DisableTaskMgr, &hk );
}
if ( bDisable ) {
// disable TM: set policy = 1
DWORD val = 1;
RegSetValueEx( hk, VAL_DisableTaskMgr, NULL, REG_DWORD, (BYTE*)&val, sizeof(val) );
} else {
// enable TM: remove policy
RegDeleteValue( hk,VAL_DisableTaskMgr );
}
}
}
+429
View File
@@ -0,0 +1,429 @@
/*
===========================================================================
Doom 3 BFG Edition GPL Source Code
Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
Doom 3 BFG Edition Source Code 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.
Doom 3 BFG Edition Source Code 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 Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
===========================================================================
*/
#pragma hdrstop
#include "../../idlib/precompiled.h"
#include "win_local.h"
#include "../../renderer/tr_local.h"
#include <Windowsx.h>
LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
static bool s_alttab_disabled;
extern idCVar r_windowX;
extern idCVar r_windowY;
extern idCVar r_windowWidth;
extern idCVar r_windowHeight;
static void WIN_DisableAltTab() {
if ( s_alttab_disabled || win32.win_allowAltTab.GetBool() ) {
return;
}
if ( !idStr::Icmp( cvarSystem->GetCVarString( "sys_arch" ), "winnt" ) ) {
RegisterHotKey( 0, 0, MOD_ALT, VK_TAB );
} else {
BOOL old;
SystemParametersInfo( SPI_SCREENSAVERRUNNING, 1, &old, 0 );
}
s_alttab_disabled = true;
}
static void WIN_EnableAltTab() {
if ( !s_alttab_disabled || win32.win_allowAltTab.GetBool() ) {
return;
}
if ( !idStr::Icmp( cvarSystem->GetCVarString( "sys_arch" ), "winnt" ) ) {
UnregisterHotKey( 0, 0 );
} else {
BOOL old;
SystemParametersInfo( SPI_SCREENSAVERRUNNING, 0, &old, 0 );
}
s_alttab_disabled = false;
}
void WIN_Sizing(WORD side, RECT *rect)
{
if ( !R_IsInitialized() || renderSystem->GetWidth() <= 0 || renderSystem->GetHeight() <= 0 ) {
return;
}
// restrict to a standard aspect ratio
int width = rect->right - rect->left;
int height = rect->bottom - rect->top;
// Adjust width/height for window decoration
RECT decoRect = { 0, 0, 0, 0 };
AdjustWindowRect( &decoRect, WINDOW_STYLE|WS_SYSMENU, FALSE );
int decoWidth = decoRect.right - decoRect.left;
int decoHeight = decoRect.bottom - decoRect.top;
width -= decoWidth;
height -= decoHeight;
// Clamp to a minimum size
if ( width < SCREEN_WIDTH / 4 ) {
width = SCREEN_WIDTH / 4;
}
if ( height < SCREEN_HEIGHT / 4 ) {
height = SCREEN_HEIGHT / 4;
}
const int minWidth = height * 4 / 3;
const int maxHeight = width * 3 / 4;
const int maxWidth = height * 16 / 9;
const int minHeight = width * 9 / 16;
// Set the new size
switch ( side ) {
case WMSZ_LEFT:
rect->left = rect->right - width - decoWidth;
rect->bottom = rect->top + idMath::ClampInt( minHeight, maxHeight, height ) + decoHeight;
break;
case WMSZ_RIGHT:
rect->right = rect->left + width + decoWidth;
rect->bottom = rect->top + idMath::ClampInt( minHeight, maxHeight, height ) + decoHeight;
break;
case WMSZ_BOTTOM:
case WMSZ_BOTTOMRIGHT:
rect->bottom = rect->top + height + decoHeight;
rect->right = rect->left + idMath::ClampInt( minWidth, maxWidth, width ) + decoWidth;
break;
case WMSZ_TOP:
case WMSZ_TOPRIGHT:
rect->top = rect->bottom - height - decoHeight;
rect->right = rect->left + idMath::ClampInt( minWidth, maxWidth, width ) + decoWidth;
break;
case WMSZ_BOTTOMLEFT:
rect->bottom = rect->top + height + decoHeight;
rect->left = rect->right - idMath::ClampInt( minWidth, maxWidth, width ) - decoWidth;
break;
case WMSZ_TOPLEFT:
rect->top = rect->bottom - height - decoHeight;
rect->left = rect->right - idMath::ClampInt( minWidth, maxWidth, width ) - decoWidth;
break;
}
}
/*
====================
MainWndProc
main window procedure
====================
*/
LONG WINAPI MainWndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {
int key;
switch( uMsg ) {
case WM_WINDOWPOSCHANGED:
if (R_IsInitialized()) {
RECT rect;
if (::GetClientRect(win32.hWnd, &rect)) {
if ( rect.right > rect.left && rect.bottom > rect.top ) {
glConfig.nativeScreenWidth = rect.right - rect.left;
glConfig.nativeScreenHeight = rect.bottom - rect.top;
// save the window size in cvars if we aren't fullscreen
int style = GetWindowLong( hWnd, GWL_STYLE );
if ( ( style & WS_POPUP ) == 0 ) {
r_windowWidth.SetInteger( glConfig.nativeScreenWidth );
r_windowHeight.SetInteger( glConfig.nativeScreenHeight );
}
}
}
}
break;
case WM_MOVE: {
int xPos, yPos;
RECT r;
// save the window origin in cvars if we aren't fullscreen
int style = GetWindowLong( hWnd, GWL_STYLE );
if ( ( style & WS_POPUP ) == 0 ) {
xPos = (short) LOWORD(lParam); // horizontal position
yPos = (short) HIWORD(lParam); // vertical position
r.left = 0;
r.top = 0;
r.right = 1;
r.bottom = 1;
AdjustWindowRect( &r, style, FALSE );
r_windowX.SetInteger( xPos + r.left );
r_windowY.SetInteger( yPos + r.top );
}
break;
}
case WM_CREATE:
win32.hWnd = hWnd;
if ( win32.cdsFullscreen ) {
WIN_DisableAltTab();
} else {
WIN_EnableAltTab();
}
// do the OpenGL setup
void GLW_WM_CREATE( HWND hWnd );
GLW_WM_CREATE( hWnd );
break;
case WM_DESTROY:
// let sound and input know about this?
win32.hWnd = NULL;
if ( win32.cdsFullscreen ) {
WIN_EnableAltTab();
}
break;
case WM_CLOSE:
soundSystem->SetMute( true );
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "quit\n" );
break;
case WM_ACTIVATE:
// if we got here because of an alt-tab or maximize,
// we should activate immediately. If we are here because
// the mouse was clicked on a title bar or drag control,
// don't activate until the mouse button is released
{
int fActive, fMinimized;
fActive = LOWORD(wParam);
fMinimized = (BOOL) HIWORD(wParam);
win32.activeApp = (fActive != WA_INACTIVE);
if ( win32.activeApp ) {
idKeyInput::ClearStates();
Sys_GrabMouseCursor( true );
if ( common->IsInitialized() ) {
SetCursor( NULL );
}
}
if ( fActive == WA_INACTIVE ) {
win32.movingWindow = false;
if ( common->IsInitialized() ) {
SetCursor( LoadCursor( 0, IDC_ARROW ) );
}
}
// start playing the game sound world
soundSystem->SetMute( !win32.activeApp );
// we do not actually grab or release the mouse here,
// that will be done next time through the main loop
}
break;
case WM_TIMER: {
if ( win32.win_timerUpdate.GetBool() ) {
common->Frame();
}
break;
}
case WM_SYSCOMMAND:
if ( wParam == SC_SCREENSAVE || wParam == SC_KEYMENU ) {
return 0;
}
break;
case WM_SYSKEYDOWN:
if ( wParam == 13 ) { // alt-enter toggles full-screen
cvarSystem->SetCVarBool( "r_fullscreen", !renderSystem->IsFullScreen() );
cmdSystem->BufferCommandText( CMD_EXEC_APPEND, "vid_restart\n" );
return 0;
}
// fall through for other keys
case WM_KEYDOWN:
key = ( ( lParam >> 16 ) & 0xFF ) | ( ( ( lParam >> 24 ) & 1 ) << 7 );
if ( key == K_LCTRL || key == K_LALT || key == K_RCTRL || key == K_RALT ) {
// let direct-input handle this because windows sends Alt-Gr
// as two events (ctrl then alt)
break;
}
// D
if ( key == K_NUMLOCK ) {
key = K_PAUSE;
} else if ( key == K_PAUSE ) {
key = K_NUMLOCK;
}
Sys_QueEvent( SE_KEY, key, true, 0, NULL, 0 );
break;
case WM_SYSKEYUP:
case WM_KEYUP:
key = ( ( lParam >> 16 ) & 0xFF ) | ( ( ( lParam >> 24 ) & 1 ) << 7 );
if ( key == K_PRINTSCREEN ) {
// don't queue printscreen keys. Since windows doesn't send us key
// down events for this, we handle queueing them with DirectInput
break;
} else if ( key == K_LCTRL || key == K_LALT || key == K_RCTRL || key == K_RALT ) {
// let direct-input handle this because windows sends Alt-Gr
// as two events (ctrl then alt)
break;
}
Sys_QueEvent( SE_KEY, key, false, 0, NULL, 0 );
break;
case WM_CHAR:
Sys_QueEvent( SE_CHAR, wParam, 0, 0, NULL, 0 );
break;
case WM_NCLBUTTONDOWN:
// win32.movingWindow = true;
break;
case WM_ENTERSIZEMOVE:
win32.movingWindow = true;
break;
case WM_EXITSIZEMOVE:
win32.movingWindow = false;
break;
case WM_SIZING:
WIN_Sizing(wParam, (RECT *)lParam);
break;
case WM_MOUSEMOVE: {
if ( !common->IsInitialized() ) {
break;
}
const bool isShellActive = ( game && ( game->Shell_IsActive() || game->IsPDAOpen() ) );
const bool isConsoleActive = console->Active();
if ( win32.activeApp ) {
if ( isShellActive ) {
// If the shell is active, it will display its own cursor.
SetCursor( NULL );
} else if ( isConsoleActive ) {
// The console is active but the shell is not.
// Show the Windows cursor.
SetCursor( LoadCursor( 0, IDC_ARROW ) );
} else {
// The shell not active and neither is the console.
// This is normal gameplay, hide the cursor.
SetCursor( NULL );
}
} else {
if ( !isShellActive ) {
// Always show the cursor when the window is in the background
SetCursor( LoadCursor( 0, IDC_ARROW ) );
} else {
SetCursor( NULL );
}
}
const int x = GET_X_LPARAM( lParam );
const int y = GET_Y_LPARAM( lParam );
// Generate an event
Sys_QueEvent( SE_MOUSE_ABSOLUTE, x, y, 0, NULL, 0 );
// Get a mouse leave message
TRACKMOUSEEVENT tme = {
sizeof( TRACKMOUSEEVENT ),
TME_LEAVE,
hWnd,
0
};
TrackMouseEvent( &tme );
return 0;
}
case WM_MOUSELEAVE: {
Sys_QueEvent( SE_MOUSE_LEAVE, 0, 0, 0, NULL, 0 );
return 0;
}
case WM_LBUTTONDOWN: {
Sys_QueEvent( SE_KEY, K_MOUSE1, 1, 0, NULL, 0 );
return 0;
}
case WM_LBUTTONUP: {
Sys_QueEvent( SE_KEY, K_MOUSE1, 0, 0, NULL, 0 );
return 0;
}
case WM_RBUTTONDOWN: {
Sys_QueEvent( SE_KEY, K_MOUSE2, 1, 0, NULL, 0 );
return 0;
}
case WM_RBUTTONUP: {
Sys_QueEvent( SE_KEY, K_MOUSE2, 0, 0, NULL, 0 );
return 0;
}
case WM_MBUTTONDOWN: {
Sys_QueEvent( SE_KEY, K_MOUSE3, 1, 0, NULL, 0 );
return 0;
}
case WM_MBUTTONUP: {
Sys_QueEvent( SE_KEY, K_MOUSE3, 0, 0, NULL, 0 );
return 0;
}
case WM_XBUTTONDOWN: {
int button = GET_XBUTTON_WPARAM( wParam );
if ( button == 1 ) {
Sys_QueEvent( SE_KEY, K_MOUSE4, 1, 0, NULL, 0 );
} else if ( button == 2 ) {
Sys_QueEvent( SE_KEY, K_MOUSE5, 1, 0, NULL, 0 );
}
return 0;
}
case WM_XBUTTONUP: {
int button = GET_XBUTTON_WPARAM( wParam );
if ( button == 1 ) {
Sys_QueEvent( SE_KEY, K_MOUSE4, 0, 0, NULL, 0 );
} else if ( button == 2 ) {
Sys_QueEvent( SE_KEY, K_MOUSE5, 0, 0, NULL, 0 );
}
return 0;
}
case WM_MOUSEWHEEL: {
int delta = GET_WHEEL_DELTA_WPARAM( wParam ) / WHEEL_DELTA;
int key = delta < 0 ? K_MWHEELDOWN : K_MWHEELUP;
delta = abs( delta );
while( delta-- > 0 ) {
Sys_QueEvent( SE_KEY, key, true, 0, NULL, 0 );
Sys_QueEvent( SE_KEY, key, false, 0, NULL, 0 );
}
break;
}
}
return DefWindowProc( hWnd, uMsg, wParam, lParam );
}