mirror of
https://github.com/id-Software/DOOM-3-BFG.git
synced 2026-06-09 15:15:54 +02:00
Initial commit
This commit is contained in:
@@ -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++;
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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__ */
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
@@ -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__
|
||||
@@ -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 ) {
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
@@ -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__ */
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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__ */
|
||||
@@ -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, "" );
|
||||
}
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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__
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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__
|
||||
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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__
|
||||
@@ -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
|
||||
|
||||
@@ -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 |
@@ -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() {
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
};
|
||||
@@ -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__ */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
@@ -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
@@ -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 );
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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__
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 );
|
||||
}
|
||||
Reference in New Issue
Block a user