Reorganized and cleaned up the solution.

This commit is contained in:
HikikoMarmy
2026-03-02 12:37:07 +00:00
parent 8012f30170
commit d4dfbddf69
175 changed files with 1516 additions and 1136 deletions

View File

@@ -0,0 +1,450 @@
#include "Common/ByteStream.hpp"
#include <codecvt>
#include <span>
ByteBuffer::ByteBuffer( const std::vector< uint8_t > &data )
{
this->m_buffer = data;
this->m_position = 0;
}
ByteBuffer::ByteBuffer( const std::string &data )
{
this->m_buffer = std::vector< uint8_t >( data.begin(), data.end() );
this->m_position = 0;
}
ByteBuffer::ByteBuffer( const uint8_t *data, uint32_t length )
{
this->m_buffer = std::vector< uint8_t >( data, data + length );
this->m_position = 0;
}
ByteBuffer::ByteBuffer( uint32_t length )
{
this->m_buffer = std::vector< uint8_t >( length, 0 );
this->m_position = 0;
}
ByteBuffer::ByteBuffer()
{
this->m_position = 0;
}
ByteBuffer::~ByteBuffer()
{
}
void ByteBuffer::resize( uint32_t size )
{
m_buffer.resize( size );
}
void ByteBuffer::shrink_to_fit()
{
m_buffer.shrink_to_fit();
}
template < typename T >
void ByteBuffer::write( T value )
{
write_bytes( ( uint8_t * )&value, sizeof( T ) );
}
template < typename T >
T ByteBuffer::read()
{
if( m_position >= m_buffer.size() )
{
return (T)0;
}
T value = *( T * )&m_buffer[ m_position ];
m_position += sizeof( T );
return value;
}
void ByteBuffer::write_utf8( const std::string &str, std::optional<uint32_t> length )
{
if( length.has_value() )
{
write_u32( length.value() );
if( length.value() > str.size() )
{
write_bytes( std::vector< uint8_t >( str.begin(), str.end() ) );
write_bytes( std::vector< uint8_t >( length.value() - str.size(), 0 ) );
}
else
{
write_bytes( std::vector< uint8_t >( str.begin(), str.begin() + length.value() ) );
}
}
else
{
write_u32( static_cast< uint32_t >( str.size() ) );
write_bytes( std::vector< uint8_t >( str.begin(), str.end() ) );
}
}
void ByteBuffer::write_utf16( const std::wstring &str, std::optional<uint32_t> length )
{
write_u32( static_cast< uint32_t >( str.size() ) );
for( wchar_t ch : str )
{
uint16_t val = static_cast< uint16_t >( ch );
write<uint8_t>( val & 0xFF );
write<uint8_t>( ( val >> 8 ) & 0xFF );
}
}
void ByteBuffer::write_sz_utf8( const std::string &str, std::optional<uint32_t> length )
{
if( length )
{
write_bytes( std::vector< uint8_t >( str.begin(), str.end() ) );
write_bytes( std::vector< uint8_t >( length.value() - str.size(), 0 ) );
}
else
{
write_bytes( std::vector< uint8_t >( str.begin(), str.end() ) );
write< uint8_t >( 0 );
}
}
void ByteBuffer::write_sz_utf16( const std::wstring &str, std::optional<uint32_t> length )
{
for( wchar_t ch : str )
{
uint16_t val = static_cast< uint16_t >( ch );
write<uint8_t>( val & 0xFF );
write<uint8_t>( ( val >> 8 ) & 0xFF );
}
if( length )
{
size_t bytesWritten = str.size() * 2;
size_t totalBytes = length.value();
if( bytesWritten < totalBytes )
{
write_bytes( std::vector<uint8_t>( totalBytes - bytesWritten, 0 ) );
}
}
else
{
write<uint16_t>( 0 );
}
}
void ByteBuffer::write_encrypted_utf8( const std::string &str )
{
auto encrypted = RealmCrypt::encryptSymmetric( std::vector< uint8_t >( str.begin(), str.end() ) );
write_u32( static_cast< uint32_t >( encrypted.size() ) + 4 );
write_u32( static_cast< uint32_t >( str.size() ) );
write_bytes( encrypted );
}
void ByteBuffer::write_encrypted_utf16( const std::wstring &str )
{
std::vector< uint8_t > utf16;
for( auto c : str )
{
utf16.push_back( c & 0xFF );
utf16.push_back( ( c >> 8 ) & 0xFF );
}
auto encrypted = RealmCrypt::encryptSymmetric( utf16 );
uint32_t encryptedLength = static_cast< uint32_t >( encrypted.size() );
uint32_t decryptedLength = static_cast< uint32_t >( utf16.size() );
// Correct blockLength: in 2-byte words, including the 4-byte decrypted length
write_u32( ( encryptedLength + 4 ) / 2 );
write_u32( decryptedLength );
write_bytes( encrypted );
}
uint8_t ByteBuffer::read_u8()
{
return read< uint8_t >();
}
uint16_t ByteBuffer::read_u16()
{
return read< uint16_t >();
}
uint32_t ByteBuffer::read_u32()
{
return read< uint32_t >();
}
int8_t ByteBuffer::read_i8()
{
return read< int8_t >();
}
int16_t ByteBuffer::read_i16()
{
return read< int16_t >();
}
int32_t ByteBuffer::read_i32()
{
return read< int32_t >();
}
float_t ByteBuffer::read_f32()
{
return read< float_t >();
}
std::string ByteBuffer::read_utf8( std::optional<uint32_t> length )
{
if( !length )
{
length = read_u32();
}
if( m_position + length.value() > m_buffer.size() )
{
throw std::runtime_error( "read_utf8: Attempt to read past end of buffer" );
}
std::string value( reinterpret_cast< const char * >( &m_buffer[ m_position ] ), length.value() );
m_position += length.value();
return value;
}
std::wstring ByteBuffer::read_utf16( std::optional<uint32_t> length )
{
if( !length )
{
length = read_u32();
}
uint32_t byteLength = length.value() * 2;
if( m_position + byteLength > m_buffer.size() )
{
throw std::runtime_error( "read_utf16: Attempt to read past end of buffer" );
}
std::wstring value;
value.reserve( length.value() );
for( size_t i = 0; i < byteLength; i += 2 )
{
uint16_t ch = m_buffer[ m_position + i ] | ( m_buffer[ m_position + i + 1 ] << 8 );
value.push_back( static_cast< wchar_t >( ch ) );
}
m_position += byteLength;
return value;
}
std::string ByteBuffer::read_sz_utf8()
{
std::string value;
while( m_buffer[ m_position ] != 0 )
{
value.push_back( m_buffer[ m_position ] );
m_position++;
}
m_position++;
return value;
}
std::wstring ByteBuffer::read_sz_utf16()
{
std::wstring value;
while( m_buffer[ m_position ] != 0 || m_buffer[ m_position + 1 ] != 0 )
{
value.push_back( m_buffer[ m_position ] | ( m_buffer[ m_position + 1 ] << 8 ) );
m_position += 2;
}
m_position += 2;
return value;
}
std::string ByteBuffer::read_encrypted_utf8( bool hasBlockLength )
{
uint32_t encryptedLength = 0;
uint32_t decryptedLength = 0;
if( hasBlockLength )
{
uint32_t blockLength = read_u32() * 2;
decryptedLength = read_u32();
encryptedLength = blockLength - 4;
}
else
{
decryptedLength = read_u32();
encryptedLength = Util::round_up( decryptedLength, 16 );
}
std::span< const uint8_t > encryptedBuffer( m_buffer.data() + m_position, encryptedLength );
m_position += encryptedLength;
if( decryptedLength == 0 )
{
return "";
}
// Decrypt the buffer
std::vector< uint8_t > decryptedBuffer = RealmCrypt::decryptSymmetric( encryptedBuffer );
std::string result( decryptedBuffer.begin(), decryptedBuffer.end() );
return result;
}
std::wstring ByteBuffer::read_encrypted_utf16( bool hasBlockLength )
{
uint32_t encryptedLength = 0;
uint32_t decryptedLength = 0;
if( hasBlockLength )
{
uint32_t blockLength = read_u32() * 2;
decryptedLength = read_u32(); // This length is already multiplied by sizeof(wchar_t)
encryptedLength = blockLength - 4;
}
else
{
decryptedLength = read_u32();
encryptedLength = Util::round_up( decryptedLength, 16 );
}
std::span< const uint8_t > encryptedBuffer( m_buffer.data() + m_position, encryptedLength );
m_position += encryptedLength;
if( decryptedLength == 0 )
{
return L"";
}
// Decrypt the buffer
std::vector< uint8_t > decryptedBuffer = RealmCrypt::decryptSymmetric( encryptedBuffer );
std::wstring result( decryptedLength / 2, L'\0' );
std::memcpy( result.data(), decryptedBuffer.data(), decryptedLength );
return result;
}
void ByteBuffer::write_bytes( const std::vector< uint8_t > &value )
{
std::copy( value.begin(), value.end(), std::back_inserter( m_buffer ) );
m_position += value.size();
}
void ByteBuffer::write_bytes( const uint8_t *value, uint32_t length )
{
std::copy( value, value + length, std::back_inserter( m_buffer ) );
m_position += length;
}
void ByteBuffer::write_encrypted_bytes( const std::vector<uint8_t> &value )
{
auto encrypted = RealmCrypt::encryptSymmetric( value );
write_u32( static_cast< uint32_t >( encrypted.size() ) + 4 );
write_u32( static_cast< uint32_t >( value.size() ) );
write_bytes( encrypted );
}
std::vector<uint8_t> ByteBuffer::read_bytes( uint32_t length )
{
std::vector<uint8_t> value( length, 0 );
std::copy( m_buffer.begin() + m_position, m_buffer.begin() + m_position + length, value.begin() );
m_position += length;
return value;
}
std::vector<uint8_t> ByteBuffer::read_encrypted_bytes( uint32_t length )
{
std::vector< uint8_t > encrypted = read_bytes( length );
auto decrypted = RealmCrypt::decryptSymmetric( encrypted );
return decrypted;
}
std::vector<uint8_t> ByteBuffer::get_buffer() const
{
return m_buffer;
}
size_t ByteBuffer::get_length() const
{
return m_buffer.size();
}
void ByteBuffer::set_position( size_t where )
{
if( where > m_buffer.size() )
{
where = m_buffer.size();
}
this->m_position = where;
}
size_t ByteBuffer::get_position() const
{
return this->m_position;
}
void ByteBuffer::write_u8( uint8_t value )
{
write< uint8_t >( value );
}
void ByteBuffer::write_u16( uint16_t value )
{
write< uint16_t >( value );
}
void ByteBuffer::write_u32( uint32_t value )
{
write< uint32_t >( value );
}
void ByteBuffer::write_i8( int8_t value )
{
write< int8_t >( value );
}
void ByteBuffer::write_i16( int16_t value )
{
write< int16_t >( value );
}
void ByteBuffer::write_i32( int32_t value )
{
write< int32_t >( value );
}
void ByteBuffer::write_f32( float_t value )
{
write< float_t >( value );
}

97
Source/Common/Utility.cpp Normal file
View File

@@ -0,0 +1,97 @@
#include <algorithm>
#include <cmath>
#include <string>
#include <cstdint>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <windows.h>
#include "Common/Utility.hpp"
int32_t Util::round_up( int32_t numToRound, int32_t multiple )
{
if( multiple == 0 )
return numToRound;
int32_t remainder = abs( numToRound ) % multiple;
if( remainder == 0 )
return numToRound;
if( numToRound < 0 )
return -( abs( numToRound ) - remainder );
else
return numToRound + multiple - remainder;
}
int32_t Util::round_down( int32_t numToRound, int32_t multiple )
{
if( multiple == 0 )
return numToRound;
int32_t remainder = abs( numToRound ) % multiple;
if( remainder == 0 )
return numToRound;
if( numToRound < 0 )
return -( abs( numToRound ) + remainder );
else
return numToRound - remainder;
}
uint16_t Util::ByteSwap( uint16_t val )
{
return ( val << 8 ) | ( val >> 8 );
}
uint32_t Util::ByteSwap( uint32_t val )
{
return ( ( val << 24 ) & 0xFF000000 ) |
( ( val << 8 ) & 0x00FF0000 ) |
( ( val >> 8 ) & 0x0000FF00 ) |
( ( val >> 24 ) & 0x000000FF );
}
std::string Util::IPFromAddr( const sockaddr_in &addr )
{
char buffer[ INET_ADDRSTRLEN ] = {};
const char *result = inet_ntop( AF_INET, &addr.sin_addr, buffer, sizeof( buffer ) );
if( result )
return std::string( buffer );
else
return {};
}
std::string Util::WideToUTF8( const std::wstring &wstr )
{
if( wstr.empty() ) return {};
int size_needed = WideCharToMultiByte(
CP_UTF8, 0, wstr.c_str(), static_cast< int >( wstr.size() ),
nullptr, 0, nullptr, nullptr
);
std::string result( size_needed, 0 );
WideCharToMultiByte(
CP_UTF8, 0, wstr.c_str(), static_cast< int >( wstr.size() ),
&result[ 0 ], size_needed, nullptr, nullptr
);
return result;
}
std::wstring Util::UTF8ToWide( const std::string &str )
{
if( str.empty() ) return {};
int size_needed = MultiByteToWideChar(
CP_UTF8, 0, str.c_str(), static_cast< int >( str.size() ),
nullptr, 0
);
std::wstring result( size_needed, 0 );
MultiByteToWideChar(
CP_UTF8, 0, str.c_str(), static_cast< int >( str.size() ),
&result[ 0 ], size_needed
);
return result;
}

View File

@@ -0,0 +1,53 @@
#include <random>
#include <sstream>
#include <iomanip>
#include <stdexcept>
#include "Crypto/PasswordHash.hpp"
std::string HexDump( const std::vector<uint8_t> &bytes )
{
std::ostringstream oss;
for( auto b : bytes )
oss << std::hex << std::setfill( '0' ) << std::setw( 2 ) << ( int )b;
return oss.str();
}
std::string HashPassword( const std::string &password, uint32_t iterations, size_t saltLen )
{
std::vector<uint8_t> salt( saltLen );
std::random_device rd;
std::mt19937 rng( rd() );
std::uniform_int_distribution<int32_t> dist( 0, 255 );
for( auto &b : salt ) b = static_cast< int32_t >( dist( rng ) );
auto derived = pbkdf2_hmac_sha256( password, salt, iterations, 32 );
return "pbkdf2$" + std::to_string( iterations ) + "$" +
Base64Encode( salt ) + "$" +
Base64Encode( derived );
}
bool VerifyPassword( const std::string &password, const std::string &storedHash )
{
auto parts = std::vector<std::string>();
std::stringstream ss( storedHash );
std::string token;
while( std::getline( ss, token, '$' ) )
parts.push_back( token );
if( parts.size() != 4 || parts[ 0 ] != "pbkdf2" )
throw std::runtime_error( "Invalid hash format" );
uint32_t iterations = std::stoul( parts[ 1 ] );
std::vector<uint8_t> salt = Base64Decode( parts[ 2 ] );
std::vector<uint8_t> expected = Base64Decode( parts[ 3 ] );
auto derived = pbkdf2_hmac_sha256( password, salt, iterations, expected.size() );
return derived == expected;
}

View File

@@ -0,0 +1,163 @@
#include "Crypto/RealmCrypt.hpp"
#include "Common/Utility.hpp"
RealmCrypt::RealmCrypt()
{
std::srand( static_cast< unsigned >( std::time( nullptr ) ) );
}
std::vector< uint8_t > RealmCrypt::generateSymmetricKey( void )
{
constexpr size_t KEY_LENGTH = 32;
std::vector< uint8_t > keyData( KEY_LENGTH, 0 );
// Generate 32 random bytes
for( size_t i = 0; i < KEY_LENGTH; ++i )
{
keyData[ i ] = static_cast< uint8_t >( std::rand() % 255 );
}
return keyData;
}
std::vector<uint8_t> RealmCrypt::getSymmetricKey( void )
{
return default_sym_key;
}
std::string RealmCrypt::encryptString( std::string &input )
{
if( input.size() % 16 != 0 )
{
input.append( 16 - ( input.size() % 16 ), '\0' );
}
rijndael aes;
auto result = aes.EncryptECB(
reinterpret_cast< const uint8_t * >( input.c_str() ),
static_cast< uint32_t >( input.size() ),
default_sym_key.data()
);
return std::string( reinterpret_cast< const char * >( result ), input.size() );
}
std::string RealmCrypt::decryptString( std::string &input )
{
if( input.size() % 16 != 0 )
{
input.append( 16 - ( input.size() % 16 ), '\0' );
}
rijndael aes;
auto result = aes.DecryptECB( reinterpret_cast< const uint8_t * >(
input.c_str() ),
static_cast< uint32_t >( input.size() ),
default_sym_key.data()
);
return std::string( reinterpret_cast< const char * >( result ), input.size() );
}
std::vector<uint8_t> RealmCrypt::encryptString( const std::wstring &input )
{
// Convert UTF-16 string to raw bytes
std::vector<uint8_t> utf16Bytes;
utf16Bytes.reserve( input.size() * 2 );
for( wchar_t ch : input )
{
utf16Bytes.push_back( static_cast< uint8_t >( ch & 0xFF ) );
utf16Bytes.push_back( static_cast< uint8_t >( ( ch >> 8 ) & 0xFF ) );
}
// Pad to nearest 16 bytes
if( utf16Bytes.size() % 16 != 0 )
{
utf16Bytes.resize( ( utf16Bytes.size() / 16 + 1 ) * 16, 0 );
}
// Encrypt using AES ECB
rijndael aes;
auto encrypted = aes.EncryptECB(
reinterpret_cast< const uint8_t * >( utf16Bytes.data() ),
static_cast< uint32_t >( utf16Bytes.size() ),
default_sym_key.data()
);
// Return the raw encrypted bytes
return std::vector<uint8_t>( encrypted, encrypted + utf16Bytes.size() );
}
std::wstring RealmCrypt::decryptString( std::vector<uint8_t> &input )
{
// Ensure input is a multiple of 16 bytes
if( input.size() % 16 != 0 )
{
input.resize( ( input.size() / 16 + 1 ) * 16, 0 );
}
rijndael aes;
auto result = aes.DecryptECB(
reinterpret_cast< const uint8_t * >( input.data() ),
static_cast< uint32_t >( input.size() ),
default_sym_key.data()
);
// Convert decrypted bytes back into a wstring
std::wstring output;
output.reserve( input.size() / 2 );
for( size_t i = 0; i + 1 < input.size(); i += 2 )
{
uint16_t ch = static_cast< uint16_t >( input[ i ] | ( input[ i + 1 ] << 8 ) );
if( ch == 0 ) break; // Optional: stop at null terminator
output.push_back( static_cast< wchar_t >( ch ) );
}
return output;
}
std::vector< uint8_t > RealmCrypt::encryptSymmetric( std::span< const uint8_t > input )
{
if( input.size() % 16 != 0 )
{
std::vector< uint8_t > paddedInput( input.begin(), input.end() );
paddedInput.resize( ( ( input.size() / 16 ) + 1 ) * 16, 0 );
input = paddedInput;
}
rijndael aes;
auto result = aes.EncryptECB( reinterpret_cast< const uint8_t * >(
input.data() ),
static_cast< uint32_t >( input.size() ),
default_sym_key.data()
);
return std::vector< uint8_t >( result, result + input.size() );
}
std::vector< uint8_t > RealmCrypt::decryptSymmetric( std::span< const uint8_t > input )
{
if( input.size() % 16 != 0 )
{
std::vector< uint8_t > paddedInput( input.begin(), input.end() );
paddedInput.resize( ( ( input.size() / 16 ) + 1 ) * 16, 0 );
input = paddedInput;
}
rijndael aes;
auto result = aes.DecryptECB( reinterpret_cast< const uint8_t * >(
input.data() ),
static_cast< uint32_t >( input.size() ),
default_sym_key.data()
);
return std::vector< uint8_t >( result, result + input.size() );
}

371
Source/Crypto/rijndael.cpp Normal file
View File

@@ -0,0 +1,371 @@
#include "Crypto/rijndael.hpp"
rijndael::rijndael()
{
this->Nk = 8;
this->Nr = 14;
}
uint8_t *rijndael::EncryptECB( const uint8_t in[], uint32_t inLen,
const uint8_t key[] )
{
CheckLength( inLen );
uint8_t *out = new uint8_t[ inLen ];
uint8_t *roundKeys = new uint8_t[ 4 * Nb * ( Nr + 1 ) ];
KeyExpansion( key, roundKeys );
for( uint32_t i = 0; i < inLen; i += blockBytesLen )
{
EncryptBlock( in + i, out + i, roundKeys );
}
delete[] roundKeys;
return out;
}
uint8_t *rijndael::DecryptECB( const uint8_t in[], uint32_t inLen,
const uint8_t key[] )
{
CheckLength( inLen );
uint8_t *out = new uint8_t[ inLen ];
uint8_t *roundKeys = new uint8_t[ 4 * Nb * ( Nr + 1 ) ];
KeyExpansion( key, roundKeys );
for( uint32_t i = 0; i < inLen; i += blockBytesLen )
{
DecryptBlock( in + i, out + i, roundKeys );
}
delete[] roundKeys;
return out;
}
void rijndael::CheckLength( uint32_t len )
{
if( len % blockBytesLen != 0 )
{
throw std::length_error( "Plaintext length must be divisible by " +
std::to_string( blockBytesLen ) );
}
}
void rijndael::EncryptBlock( const uint8_t in[], uint8_t out[],
uint8_t *roundKeys )
{
uint8_t state[ 4 ][ Nb ];
uint32_t i, j, round;
for( i = 0; i < 4; i++ )
{
for( j = 0; j < Nb; j++ )
{
state[ i ][ j ] = in[ i + 4 * j ];
}
}
AddRoundKey( state, roundKeys );
for( round = 1; round <= Nr - 1; round++ )
{
SubBytes( state );
ShiftRows( state );
MixColumns( state );
AddRoundKey( state, roundKeys + round * 4 * Nb );
}
SubBytes( state );
ShiftRows( state );
AddRoundKey( state, roundKeys + Nr * 4 * Nb );
for( i = 0; i < 4; i++ )
{
for( j = 0; j < Nb; j++ )
{
out[ i + 4 * j ] = state[ i ][ j ];
}
}
}
void rijndael::DecryptBlock( const uint8_t in[], uint8_t out[],
uint8_t *roundKeys )
{
uint8_t state[ 4 ][ Nb ];
uint32_t i, j, round;
for( i = 0; i < 4; i++ )
{
for( j = 0; j < Nb; j++ )
{
state[ i ][ j ] = in[ i + 4 * j ];
}
}
AddRoundKey( state, roundKeys + Nr * 4 * Nb );
for( round = Nr - 1; round >= 1; round-- )
{
InvSubBytes( state );
InvShiftRows( state );
AddRoundKey( state, roundKeys + round * 4 * Nb );
InvMixColumns( state );
}
InvSubBytes( state );
InvShiftRows( state );
AddRoundKey( state, roundKeys );
for( i = 0; i < 4; i++ )
{
for( j = 0; j < Nb; j++ )
{
out[ i + 4 * j ] = state[ i ][ j ];
}
}
}
void rijndael::SubBytes( uint8_t state[ 4 ][ Nb ] )
{
uint32_t i, j;
uint8_t t;
for( i = 0; i < 4; i++ )
{
for( j = 0; j < Nb; j++ )
{
t = state[ i ][ j ];
state[ i ][ j ] = sbox[ t / 16 ][ t % 16 ];
}
}
}
void rijndael::ShiftRow( uint8_t state[ 4 ][ Nb ], uint32_t i,
uint32_t n ) // shift row i on n write_positions
{
uint8_t tmp[ Nb ];
for( uint32_t j = 0; j < Nb; j++ )
{
tmp[ j ] = state[ i ][ ( j + n ) % Nb ];
}
memcpy( state[ i ], tmp, Nb * sizeof( uint8_t ) );
}
void rijndael::ShiftRows( uint8_t state[ 4 ][ Nb ] )
{
ShiftRow( state, 1, 1 );
ShiftRow( state, 2, 2 );
ShiftRow( state, 3, 3 );
}
uint8_t rijndael::xtime( uint8_t b ) // multiply on x
{
return ( b << 1 ) ^ ( ( ( b >> 7 ) & 1 ) * 0x1b );
}
void rijndael::MixColumns( uint8_t state[ 4 ][ Nb ] )
{
uint8_t temp_state[ 4 ][ Nb ];
for( size_t i = 0; i < 4; ++i )
{
memset( temp_state[ i ], 0, 4 );
}
for( size_t i = 0; i < 4; ++i )
{
for( size_t k = 0; k < 4; ++k )
{
for( size_t j = 0; j < 4; ++j )
{
if( CMDS[ i ][ k ] == 1 )
temp_state[ i ][ j ] ^= state[ k ][ j ];
else
temp_state[ i ][ j ] ^= GF_MUL_TABLE[ CMDS[ i ][ k ] ][ state[ k ][ j ] ];
}
}
}
for( size_t i = 0; i < 4; ++i )
{
memcpy( state[ i ], temp_state[ i ], 4 );
}
}
void rijndael::AddRoundKey( uint8_t state[ 4 ][ Nb ], uint8_t *key )
{
uint32_t i, j;
for( i = 0; i < 4; i++ )
{
for( j = 0; j < Nb; j++ )
{
state[ i ][ j ] = state[ i ][ j ] ^ key[ i + 4 * j ];
}
}
}
void rijndael::SubWord( uint8_t *a )
{
int i;
for( i = 0; i < 4; i++ )
{
a[ i ] = sbox[ a[ i ] / 16 ][ a[ i ] % 16 ];
}
}
void rijndael::RotWord( uint8_t *a )
{
uint8_t c = a[ 0 ];
a[ 0 ] = a[ 1 ];
a[ 1 ] = a[ 2 ];
a[ 2 ] = a[ 3 ];
a[ 3 ] = c;
}
void rijndael::XorWords( uint8_t *a, uint8_t *b, uint8_t *c )
{
int i;
for( i = 0; i < 4; i++ )
{
c[ i ] = a[ i ] ^ b[ i ];
}
}
void rijndael::Rcon( uint8_t *a, uint32_t n )
{
uint32_t i;
uint8_t c = 1;
for( i = 0; i < n - 1; i++ )
{
c = xtime( c );
}
a[ 0 ] = c;
a[ 1 ] = a[ 2 ] = a[ 3 ] = 0;
}
void rijndael::KeyExpansion( const uint8_t key[], uint8_t w[] )
{
uint8_t temp[ 4 ];
uint8_t rcon[ 4 ];
uint32_t i = 0;
while( i < 4 * Nk )
{
w[ i ] = key[ i ];
i++;
}
i = 4 * Nk;
while( i < 4 * Nb * ( Nr + 1 ) )
{
temp[ 0 ] = w[ i - 4 + 0 ];
temp[ 1 ] = w[ i - 4 + 1 ];
temp[ 2 ] = w[ i - 4 + 2 ];
temp[ 3 ] = w[ i - 4 + 3 ];
if( i / 4 % Nk == 0 )
{
RotWord( temp );
SubWord( temp );
Rcon( rcon, i / ( Nk * 4 ) );
XorWords( temp, rcon, temp );
}
else if( Nk > 6 && i / 4 % Nk == 4 )
{
SubWord( temp );
}
w[ i + 0 ] = w[ i - 4 * Nk ] ^ temp[ 0 ];
w[ i + 1 ] = w[ i + 1 - 4 * Nk ] ^ temp[ 1 ];
w[ i + 2 ] = w[ i + 2 - 4 * Nk ] ^ temp[ 2 ];
w[ i + 3 ] = w[ i + 3 - 4 * Nk ] ^ temp[ 3 ];
i += 4;
}
}
void rijndael::InvSubBytes( uint8_t state[ 4 ][ Nb ] )
{
uint32_t i, j;
uint8_t t;
for( i = 0; i < 4; i++ )
{
for( j = 0; j < Nb; j++ )
{
t = state[ i ][ j ];
state[ i ][ j ] = inv_sbox[ t / 16 ][ t % 16 ];
}
}
}
void rijndael::InvMixColumns( uint8_t state[ 4 ][ Nb ] )
{
uint8_t temp_state[ 4 ][ Nb ];
for( size_t i = 0; i < 4; ++i )
{
memset( temp_state[ i ], 0, 4 );
}
for( size_t i = 0; i < 4; ++i )
{
for( size_t k = 0; k < 4; ++k )
{
for( size_t j = 0; j < 4; ++j )
{
temp_state[ i ][ j ] ^= GF_MUL_TABLE[ INV_CMDS[ i ][ k ] ][ state[ k ][ j ] ];
}
}
}
for( size_t i = 0; i < 4; ++i )
{
memcpy( state[ i ], temp_state[ i ], 4 );
}
}
void rijndael::InvShiftRows( uint8_t state[ 4 ][ Nb ] )
{
ShiftRow( state, 1, Nb - 1 );
ShiftRow( state, 2, Nb - 2 );
ShiftRow( state, 3, Nb - 3 );
}
void rijndael::XorBlocks( const uint8_t *a, const uint8_t *b,
uint8_t *c, uint32_t len )
{
for( uint32_t i = 0; i < len; i++ )
{
c[ i ] = a[ i ] ^ b[ i ];
}
}
std::vector<uint8_t> rijndael::ArrayToVector( uint8_t *a,
uint32_t len )
{
std::vector<uint8_t> v( a, a + len * sizeof( uint8_t ) );
return v;
}
uint8_t *rijndael::VectorToArray( std::vector<uint8_t> &a )
{
return a.data();
}
std::vector<uint8_t> rijndael::EncryptECB( std::vector<uint8_t> in,
std::vector<uint8_t> key )
{
uint8_t *out = EncryptECB( VectorToArray( in ), ( uint32_t )in.size(),
VectorToArray( key ) );
std::vector<uint8_t> v = ArrayToVector( out, static_cast< uint32_t >( in.size() ) );
delete[] out;
return v;
}
std::vector<uint8_t> rijndael::DecryptECB( std::vector<uint8_t> in,
std::vector<uint8_t> key )
{
uint8_t *out = DecryptECB( VectorToArray( in ), ( uint32_t )in.size(),
VectorToArray( key ) );
std::vector<uint8_t> v = ArrayToVector( out, ( uint32_t )in.size() );
delete[] out;
return v;
}

View File

@@ -0,0 +1,624 @@
#include "Database/Database.hpp"
#include "Crypto/PasswordHash.hpp"
#include "Game/RealmCharacter.hpp"
#include "Game/RealmCharacterMetaKV.hpp"
#include "logging.hpp"
#include <filesystem>
namespace fs = std::filesystem;
Database::Database()
{
const fs::path dbPath = "./database/game.db";
const fs::path dbDir = dbPath.parent_path();
std::error_code ec;
if( !dbDir.empty() && !fs::exists( dbDir ) )
{
if( !fs::create_directories( dbDir, ec ) )
{
throw std::runtime_error(
"Failed to create database directory: " + ec.message()
);
}
}
if( sqlite3_open( dbPath.string().c_str(), &m_db ) != SQLITE_OK )
{
throw std::runtime_error(
"Failed to open DB: " + std::string( sqlite3_errmsg( m_db ) )
);
}
CreateTables();
PrepareStatements();
}
Database::~Database()
{
FinalizeStatements();
if( m_db )
sqlite3_close( m_db );
}
void Database::CreateTables()
{
Execute( "CREATE TABLE IF NOT EXISTS RealmUsers ("
"account_id INTEGER PRIMARY KEY AUTOINCREMENT,"
"username TEXT UNIQUE NOT NULL,"
"password TEXT NOT NULL,"
"email_address TEXT NOT NULL,"
"date_of_birth NOT NULL,"
"chat_handle TEXT NOT NULL)" );
Execute( "CREATE TABLE IF NOT EXISTS RealmCharacters ("
"account_id INTEGER NOT NULL,"
"character_id INTEGER PRIMARY KEY AUTOINCREMENT,"
"meta_data BLOB NOT NULL,"
"character_data BLOB NOT NULL)" );
Execute( "CREATE TABLE IF NOT EXISTS RealmSession ("
"account_id INTEGER NOT NULL,"
"session_id TEXT NOT NULL UNIQUE,"
"character_id INTEGER NOT NULL,"
"ip_address TEXT NOT NULL,"
"expire_time INTEGER NOT NULL,"
"PRIMARY KEY(session_id))" );
Execute( "CREATE TABLE IF NOT EXISTS UserFriendList ("
"account_id INTEGER NOT NULL,"
"friend_handle TEXT NOT NULL,"
"PRIMARY KEY(account_id, friend_handle))" );
Execute( "CREATE TABLE IF NOT EXISTS UserIgnoredList ("
"account_id INTEGER NOT NULL,"
"ignore_handle TEXT NOT NULL,"
"PRIMARY KEY(account_id, ignore_handle))" );
}
void Database::PrepareStatements()
{
const std::vector<std::pair<QueryID, const char *>> queries = {
{ QueryID::CreateAccount,
"INSERT INTO RealmUsers ( username, password, email_address, date_of_birth, chat_handle ) VALUES ( ?, ?, ?, ?, ? );" },
{ QueryID::VerifyAccount,
"SELECT account_id, username, password, chat_handle FROM RealmUsers WHERE username = ?;" },
{ QueryID::LoadAccount,
"SELECT chat_handle FROM RealmUsers WHERE account_id = ?;" },
{ QueryID::CreateNewCharacter,
"INSERT INTO RealmCharacters ( account_id, meta_data, character_data ) VALUES ( ?, ?, ? );" },
{ QueryID::SaveCharacter,
"UPDATE RealmCharacters SET meta_data = ?, character_data = ? WHERE account_id = ? AND character_id = ?;" },
{ QueryID::LoadCharacter,
"SELECT character_id, meta_data, character_data FROM RealmCharacters WHERE account_id = ? AND character_id = ?;" },
{ QueryID::LoadCharacterSlots,
"SELECT character_id, meta_data FROM RealmCharacters WHERE account_id = ? ORDER BY character_id;" },
{ QueryID::SaveFriend,
"INSERT OR IGNORE INTO UserFriendList ( account_id, friend_handle ) VALUES ( ?, ? );" },
{ QueryID::RemoveFriend,
"DELETE FROM UserFriendList WHERE account_id = ? AND friend_handle = ?;" },
{ QueryID::LoadFriendList,
"SELECT friend_handle FROM UserFriendList WHERE account_id = ?;" },
{ QueryID::SaveIgnore,
"INSERT OR IGNORE INTO UserIgnoredList ( account_id, ignore_handle ) VALUES ( ?, ? );" },
{ QueryID::RemoveIgnore,
"DELETE FROM UserIgnoredList WHERE account_id = ? AND ignore_handle = ?;" },
{ QueryID::LoadIgnoreList,
"SELECT ignore_handle FROM UserIgnoredList WHERE account_id = ?;" }
};
try
{
for( const auto &[id, sql] : queries )
{
sqlite3_stmt *stmt = nullptr;
if( sqlite3_prepare_v2( m_db, sql, -1, &stmt, nullptr ) != SQLITE_OK )
{
throw std::runtime_error( "Failed to prepare statement: " + std::string( sqlite3_errmsg( m_db ) ) );
}
m_statements[ id ] = stmt;
}
}
catch( const std::exception &e )
{
FinalizeStatements();
Log::Error( "Database error: {}", std::string( e.what() ) );
}
}
void Database::FinalizeStatements()
{
if( m_db == nullptr )
return;
for( const auto &[id, stmt] : m_statements )
{
if( stmt ) sqlite3_finalize( stmt );
}
m_statements.clear();
}
void Database::Execute( const char *sql )
{
try
{
char *errMsg = nullptr;
if( sqlite3_exec( m_db, sql, nullptr, nullptr, &errMsg ) != SQLITE_OK )
{
std::string error = "SQL execution failed: " + std::string( errMsg );
sqlite3_free( errMsg );
throw std::runtime_error( error );
}
}
catch( const std::exception &e )
{
Log::Error( "Database error: {}", std::string( e.what() ) );
}
}
int64_t Database::CreateNewAccount( const std::string &username,
const std::string &password,
const std::string &email_address,
const std::string &date_of_birth,
const std::string &chat_handle )
{
try
{
auto hashedPassword = HashPassword( password, 1000, 32 );
auto stmt = m_statements[ QueryID::CreateAccount ];
SQLiteTransaction tx( m_db );
{
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_text( stmt, 1, username.c_str(), -1, SQLITE_TRANSIENT );
sqlite3_bind_text( stmt, 2, hashedPassword.c_str(), -1, SQLITE_TRANSIENT );
sqlite3_bind_text( stmt, 3, email_address.c_str(), -1, SQLITE_TRANSIENT );
sqlite3_bind_text( stmt, 4, date_of_birth.c_str(), -1, SQLITE_TRANSIENT );
sqlite3_bind_text( stmt, 5, chat_handle.c_str(), -1, SQLITE_TRANSIENT );
if( sqlite3_step( stmt ) != SQLITE_DONE )
{
throw std::runtime_error( "Insert failed: " + std::string( sqlite3_errmsg( m_db ) ) );
}
}
tx.commit();
return sqlite3_last_insert_rowid( m_db );
}
catch( const std::exception &e )
{
Log::Error( "Database error: " + std::string( e.what() ) );
}
return 0;
}
std::tuple< bool, int64_t, std::wstring >
Database::VerifyAccount( const std::wstring &username, const std::wstring &password )
{
try
{
auto stmt = m_statements[ QueryID::VerifyAccount ];
auto username_utf8 = Util::WideToUTF8( username );
auto password_utf8 = Util::WideToUTF8( password );
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_text( stmt, 1, username_utf8.c_str(), -1, SQLITE_TRANSIENT );
// Execute the statement
if( sqlite3_step( stmt ) == SQLITE_ROW )
{
int64_t accountId = sqlite3_column_int64( stmt, 0 );
const char *dbUsername = reinterpret_cast< const char * >( sqlite3_column_text( stmt, 1 ) );
const char *dbPassword = reinterpret_cast< const char * >( sqlite3_column_text( stmt, 2 ) );
const char *dbChatHandle = reinterpret_cast< const char * >( sqlite3_column_text( stmt, 3 ) );
if( username_utf8 == dbUsername && VerifyPassword( password_utf8, dbPassword ) )
{
Log::Debug( "Account verified: {} (ID: {})", username, accountId );
return std::make_tuple( true, accountId, Util::UTF8ToWide( dbChatHandle ) );
}
else
{
Log::Debug( "Invalid credentials for account ID: {}", accountId );
return std::make_tuple( false, -1, L"" );
}
}
else if( sqlite3_step( stmt ) == SQLITE_DONE )
{
return std::make_tuple( false, -1, L"" ); // No matching account found
}
else
{
throw std::runtime_error( "Query failed: " + std::string( sqlite3_errmsg( m_db ) ) );
}
}
catch( const std::exception &e )
{
Log::Error( "Database error: {}", std::string( e.what() ) );
}
return std::make_tuple( false, -1, L"" );
}
uint32_t Database::CreateNewCharacter( const int64_t account_id, const CharacterSlotData meta, const std::vector< uint8_t > &blob )
{
if( account_id <= 0 || meta.empty() || blob.empty() )
{
Log::Error( "Invalid parameters for CreateNewCharacter" );
return 0;
}
try
{
auto stmt = m_statements[ QueryID::CreateNewCharacter ];
const auto meta_data = meta.Serialize();
SQLiteTransaction tx( m_db );
{
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_int64( stmt, 1, account_id );
sqlite3_bind_blob( stmt, 2, meta_data.data(), static_cast< int >( meta_data.size() ), SQLITE_STATIC );
sqlite3_bind_blob( stmt, 3, blob.data(), static_cast< int >( blob.size() ), SQLITE_STATIC );
int rc = sqlite3_step( stmt );
if( rc != SQLITE_DONE )
{
Log::Error( "SQLite insert failed: {}", sqlite3_errmsg( m_db ) );
return 0;
}
}
tx.commit();
uint32_t character_id = static_cast< uint32_t >( sqlite3_last_insert_rowid( m_db ) );
return character_id;
}
catch( const std::exception &e )
{
Log::Error( "Database error: {}", std::string( e.what() ) );
}
return 0;
}
bool Database::SaveCharacter( const int64_t account_id, const int32_t character_id, const CharacterSlotData meta, const std::vector< uint8_t > &blob )
{
if( account_id <= 0 || character_id <= 0 || meta.empty() || blob.empty() )
{
Log::Error( "Invalid parameters for SaveCharacter" );
return false;
}
try
{
auto stmt = m_statements[ QueryID::SaveCharacter ];
const auto meta_data = meta.Serialize();
SQLiteTransaction tx( m_db );
{
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_blob( stmt, 1, meta_data.data(), static_cast< int >( meta_data.size() ), SQLITE_STATIC );
sqlite3_bind_blob( stmt, 2, blob.data(), static_cast< int >( blob.size() ), SQLITE_STATIC );
sqlite3_bind_int64( stmt, 3, account_id );
sqlite3_bind_int( stmt, 4, character_id );
if( sqlite3_step( stmt ) != SQLITE_DONE )
{
throw std::runtime_error( "Update failed: " + std::string( sqlite3_errmsg( m_db ) ) );
}
}
tx.commit();
return true;
}
catch( const std::exception &e )
{
Log::Error( "Database error: {}", std::string( e.what() ) );
}
return false;
}
std::map< uint32_t, CharacterSlotData > Database::LoadCharacterSlots( const int64_t account_id )
{
std::map< uint32_t, CharacterSlotData > result;
try
{
auto stmt = m_statements[ QueryID::LoadCharacterSlots ];
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_int64( stmt, 1, account_id );
while( sqlite3_step( stmt ) == SQLITE_ROW )
{
const auto character_id = sqlite3_column_int( stmt, 0 );
const auto *slot_data_blob = static_cast< const uint8_t * >( sqlite3_column_blob( stmt, 1 ) );
const auto slot_data_size = sqlite3_column_bytes( stmt, 1 );
if( slot_data_blob == nullptr || slot_data_size == 0 )
{
Log::Error( "Character ID {} has no metadata", character_id );
continue;
}
CharacterSlotData slot_data( std::vector< uint8_t >( slot_data_blob, slot_data_blob + slot_data_size ) );
result.insert( { character_id, slot_data } );
}
}
catch( const std::exception &e )
{
Log::Error( "Database error: {}", std::string( e.what() ) );
result.clear();
}
return result;
}
sptr_realm_character Database::LoadCharacterData( const int64_t account_id, const int32_t character_id )
{
try
{
auto data = std::make_shared< RealmCharacter >();
auto stmt = m_statements[ QueryID::LoadCharacter ];
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_int64( stmt, 1, account_id );
sqlite3_bind_int( stmt, 2, character_id );
if( sqlite3_step( stmt ) == SQLITE_ROW )
{
const auto character_id = sqlite3_column_int( stmt, 0 );
const auto *meta_data_blob = static_cast< const uint8_t * >( sqlite3_column_blob( stmt, 1 ) );
const auto meta_data_size = sqlite3_column_bytes( stmt, 1 );
if( meta_data_blob && meta_data_size > 0 )
{
data->Deserialize( std::vector< uint8_t >( meta_data_blob, meta_data_blob + meta_data_size ) );
}
const auto *character_blob = static_cast< const uint8_t * >( sqlite3_column_blob( stmt, 2 ) );
const auto character_size = sqlite3_column_bytes( stmt, 2 );
if( character_blob && character_size > 0 )
{
data->m_characterId = character_id;
data->m_data = std::vector< uint8_t >( character_blob, character_blob + character_size );
}
Log::Debug( "Character data loaded for account ID: {} and character ID: {}", account_id, character_id );
return data;
}
else
{
Log::Error( "No character data found for account ID: {} and character ID: {}", account_id, character_id );
}
}
catch( const std::exception &e )
{
Log::Error( "Database error: {}", std::string( e.what() ) );
}
return nullptr;
}
bool Database::SaveFriend( const int64_t account_id, const std::wstring &friend_handle )
{
if( account_id <= 0 || friend_handle.empty() )
{
Log::Error( "Invalid parameters for SaveFriend" );
return false;
}
try
{
auto stmt = m_statements[ QueryID::SaveFriend ];
auto friendHandle = Util::WideToUTF8( friend_handle );
SQLiteTransaction tx( m_db );
{
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_int64( stmt, 1, account_id );
sqlite3_bind_text( stmt, 2, friendHandle.c_str(), -1, SQLITE_TRANSIENT );
if( sqlite3_step( stmt ) != SQLITE_DONE )
{
Log::Error( "SQLite insert failed: {}", sqlite3_errmsg( m_db ) );
return false;
}
}
tx.commit();
return true;
}
catch( const std::exception &e )
{
Log::Error( "Database error: {}", std::string( e.what() ) );
}
return false;
}
bool Database::RemoveFriend( const int64_t account_id, const std::wstring &friend_handle )
{
if( account_id <= 0 || friend_handle.empty() )
{
Log::Error( "Invalid parameters for RemoveFriend" );
return false;
}
try
{
auto stmt = m_statements[ QueryID::RemoveFriend ];
auto friendHandle = Util::WideToUTF8( friend_handle );
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_int64( stmt, 1, account_id );
sqlite3_bind_text( stmt, 2, friendHandle.c_str(), -1, SQLITE_TRANSIENT );
if( sqlite3_step( stmt ) != SQLITE_DONE )
{
Log::Error( "SQLite delete failed: {}", sqlite3_errmsg( m_db ) );
return false;
}
return true;
}
catch( const std::exception &e )
{
Log::Error( "Database error: {}", std::string( e.what() ) );
}
return false;
}
std::vector<std::wstring> Database::LoadFriends( const int64_t account_id )
{
std::vector<std::wstring> friend_list;
try
{
auto stmt = m_statements[ QueryID::LoadFriendList ];
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_int64( stmt, 1, account_id );
while( sqlite3_step( stmt ) == SQLITE_ROW )
{
const char *friendHandle = reinterpret_cast< const char * >( sqlite3_column_text( stmt, 0 ) );
if( friendHandle )
{
friend_list.push_back( Util::UTF8ToWide( friendHandle ) );
}
}
}
catch( const std::exception &e )
{
Log::Error( "Database error: {}", std::string( e.what() ) );
}
return friend_list;
}
bool Database::SaveIgnore( const int64_t account_id, const std::wstring &ignore_handle )
{
if( account_id <= 0 || ignore_handle.empty() )
{
Log::Error( "Invalid parameters for SaveIgnore" );
return false;
}
try
{
auto stmt = m_statements[ QueryID::SaveIgnore ];
auto friendHandle = Util::WideToUTF8( ignore_handle );
SQLiteTransaction tx( m_db );
{
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_int64( stmt, 1, account_id );
sqlite3_bind_text( stmt, 2, friendHandle.c_str(), -1, SQLITE_TRANSIENT );
if( sqlite3_step( stmt ) != SQLITE_DONE )
{
Log::Error( "SQLite insert failed: {}", sqlite3_errmsg( m_db ) );
return false;
}
}
tx.commit();
return true;
}
catch( const std::exception &e )
{
Log::Error( "Database error: {}", std::string( e.what() ) );
}
return false;
}
bool Database::RemoveIgnore( const int64_t account_id, const std::wstring &ignore_handle )
{
if( account_id <= 0 || ignore_handle.empty() )
{
Log::Error( "Invalid parameters for RemoveIgnore" );
return false;
}
try
{
auto stmt = m_statements[ QueryID::RemoveIgnore ];
auto ignoreHandle = Util::WideToUTF8( ignore_handle );
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_int64( stmt, 1, account_id );
sqlite3_bind_text( stmt, 2, ignoreHandle.c_str(), -1, SQLITE_TRANSIENT );
if( sqlite3_step( stmt ) != SQLITE_DONE )
{
Log::Error( "SQLite delete failed: {}", sqlite3_errmsg( m_db ) );
return false;
}
return true;
}
catch( const std::exception &e )
{
Log::Error( "Database error: {}", std::string( e.what() ) );
}
return false;
}
std::vector<std::wstring> Database::LoadIgnores( const int64_t account_id )
{
std::vector<std::wstring> ignore_list;
try
{
auto stmt = m_statements[ QueryID::LoadIgnoreList ];
sqlite3_reset( stmt );
sqlite3_clear_bindings( stmt );
sqlite3_bind_int64( stmt, 1, account_id );
while( sqlite3_step( stmt ) == SQLITE_ROW )
{
const char *ignoreHandle = reinterpret_cast< const char * >( sqlite3_column_text( stmt, 0 ) );
if( ignoreHandle )
{
ignore_list.push_back( Util::UTF8ToWide( ignoreHandle ) );
}
}
}
catch( const std::exception &e )
{
Log::Error( "Database error: {}", std::string( e.what() ) );
}
return ignore_list;
}

View File

@@ -0,0 +1,153 @@
// ╔═╗ ╦ ╦ ╔═╗ ╔╦╗ ╔═╗ ╦ ╔═╗ ╔╗╔ ╔═╗
// ║ ╠═╣ ╠═╣ ║║║ ╠═╝ ║ ║ ║ ║║║ ╚═╗
// ╚═╝ ╩ ╩ ╩ ╩ ╩ ╩ ╩ ╩ ╚═╝ ╝╚╝ ╚═╝
// ╔╦╗╦╔═╗╔═╗╔═╗╦ ╦╔═╗╦═╗╦ ╦ ╔═╗╔═╗╦═╗╦ ╦╔═╗╦═╗
// ║║║╚═╗║ ║ ║╚╗╔╝║╣ ╠╦╝╚╦╝ ╚═╗║╣ ╠╦╝╚╗╔╝║╣ ╠╦╝
// ═╩╝╩╚═╝╚═╝╚═╝ ╚╝ ╚═╝╩╚═ ╩ ╚═╝╚═╝╩╚═ ╚╝ ╚═╝╩╚═
#include "Discovery Server/DiscoveryServer.hpp"
#include "Common/ByteStream.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "logging.hpp"
DiscoveryServer::DiscoveryServer()
{
m_running = false;
m_socket = INVALID_SOCKET;
m_recvBuffer.resize( 1024 );
}
DiscoveryServer::~DiscoveryServer()
{
Log::Info( "Discovery Server stopped." );
}
void DiscoveryServer::Start( std::string ip, int32_t port )
{
m_socket = socket( AF_INET, SOCK_DGRAM, 0 );
if( m_socket == INVALID_SOCKET )
{
Log::Error( "Failed to create socket." );
return;
}
sockaddr_in service{};
service.sin_family = AF_INET;
service.sin_port = htons( port );
if( ip == "0.0.0.0" )
{
service.sin_addr.s_addr = INADDR_ANY;
}
else
{
if( InetPtonA( AF_INET, ip.c_str(), &service.sin_addr ) != 1 )
{
Log::Error( "Invalid IP address format: {}", ip );
closesocket( m_socket );
return;
}
}
if( bind( m_socket, reinterpret_cast< SOCKADDR * >( &service ), sizeof( service ) ) == SOCKET_ERROR )
{
Log::Error( "Failed to bind socket." );
closesocket( m_socket );
return;
}
m_running = true;
m_thread = std::thread( &DiscoveryServer::Run, this );
Log::Info( "Discovery Server started {}:{}", ip, port );
}
void DiscoveryServer::Stop()
{
m_running = false;
if( m_thread.joinable() )
{
m_thread.join();
}
}
void DiscoveryServer::Run()
{
sockaddr_in clientAddr;
int clientAddrLen = sizeof( clientAddr );
while( m_running )
{
std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
auto bytesReceived = recvfrom( m_socket, ( char * )m_recvBuffer.data(), 1024, 0, ( struct sockaddr * )&clientAddr, &clientAddrLen );
if( bytesReceived == SOCKET_ERROR || bytesReceived < 4 )
{
continue;
}
ProcessPacket( &clientAddr, std::make_shared< ByteBuffer >( m_recvBuffer.data(), bytesReceived ) );
}
}
void DiscoveryServer::ProcessPacket( sockaddr_in *clientAddr, sptr_byte_stream stream )
{
if( 0x20 != stream->read_u32() )
return;
auto encryptedBytes = stream->read_bytes( 0x20 );
auto decryptedBytes = RealmCrypt::decryptSymmetric( encryptedBytes );
std::wstring sessionId( 16, L'\0' );
std::memcpy( sessionId.data(), decryptedBytes.data(), 0x20 );
// Validate the session ID is 16 characters long and not all 0's
if( sessionId.size() != 16 || std::all_of( sessionId.begin(), sessionId.end(), []( wchar_t ch )
{
return ch == L'\0';
} ) )
{
Log::Error( "Invalid session id." );
return;
}
// Get the users remote IP and Port for discovery.
char remoteIp[ INET_ADDRSTRLEN ];
InetNtopA( AF_INET, &clientAddr->sin_addr, remoteIp, INET_ADDRSTRLEN );
uint16_t remotePort = ntohs( clientAddr->sin_port );
// Find the user associated with the session ID
auto user = UserManager::Get().FindUserBySessionId( sessionId );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", sessionId );
return;
}
if( remoteIp != user->sock->remote_ip )
{
Log::Error( "Discovery Handshake from invalid IP!" );
return;
}
// Initialize our discovery information
user->m_discoveryAddr = remoteIp;
user->m_discoveryPort = remotePort;
Log::Debug( "Discovery Handshake from {}:{}", remoteIp, remotePort );
if( user->m_isHost )
{
GameSessionManager::Get().RequestOpen( user );
}
else
{
GameSessionManager::Get().RequestJoin( user );
}
}

View File

@@ -0,0 +1,203 @@
/*
* CharacterSaveManager is responsible for managing character saves while online.
* Because Return To Arms can have any user in the party initiate a save for any
* other user in the party, we need to keep track of these tasks.
*
* Tasks keep track of the user that initiated the save (the owner),
* and the target user whose character is being saved.
*/
#include "Game/CharacterSaveManager.hpp"
#include "Game/RealmUser.hpp"
#include "Game/RealmUserManager.hpp"
#include "Database/Database.hpp"
#include "logging.hpp"
CharacterSaveManager::CharacterSaveManager()
{
m_tasks.clear();
}
CharacterSaveManager::~CharacterSaveManager()
{
m_tasks.clear();
}
bool CharacterSaveManager::BeginSaveTask(
const sptr_user user,
const uint32_t characterId,
const CharacterSlotData &metaData,
const CharacterSaveType saveType )
{
return BeginSaveTask( user, user, characterId, metaData, saveType );
}
bool CharacterSaveManager::BeginSaveTask(
const sptr_user m_owner,
const sptr_user m_target,
const uint32_t characterId,
const CharacterSlotData &metaData,
const CharacterSaveType saveType )
{
if( !m_owner || !m_target )
{
Log::Error( "CreateSaveTask called with empty session IDs!" );
return false;
}
const auto &sessionId = m_owner->m_sessionId;
if( m_tasks.find( sessionId ) != m_tasks.end() )
{
m_tasks.erase( sessionId );
}
auto task = std::make_shared< CharacterSaveTask >( saveType, characterId );
task->m_ownerUser = m_owner;
task->m_targetUser = m_target;
task->m_meta = metaData;
m_tasks[ sessionId ] = task;
return true;
}
void CharacterSaveManager::AppendSaveData( const std::wstring &sessionId, const std::vector<uint8_t> &data, bool endOfData )
{
try
{
auto it = m_tasks.find( sessionId );
if( it == m_tasks.end() )
{
Log::Error( "AddDataToSaveTask: No task found for session ID [{}].", sessionId );
return;
}
auto &task = it->second;
if( !task->AppendData( data ) )
{
Log::Error( "AddDataToSaveTask: Append failed for session ID [{}].", sessionId );
return;
}
if( !endOfData )
{
return;
}
if( !task->Validate() )
{
Log::Error( "AddDataToSaveTask: Validation failed for session ID [{}].", sessionId );
return;
}
if( !CommitSaveTask( sessionId ) )
{
Log::Error( "AddDataToSaveTask: Commit failed for session ID [{}].", sessionId );
return;
}
Log::Debug( "AddDataToSaveTask: Final chunk committed for session ID [{}].", sessionId );
}
catch( const std::exception &e )
{
Log::Error( "AddDataToSaveTask: Exception for session ID [{}]: {}", sessionId, e.what() );
}
}
bool CharacterSaveManager::CommitSaveTask( const std::wstring &sessionId )
{
try
{
auto node = m_tasks.extract( sessionId );
if( !node )
{
Log::Error( "CommitSaveTask: Task for session ID [{}] not found.", sessionId );
return false;
}
const auto &saveTask = node.mapped();
if( !saveTask->Validate() )
{
return false;
}
auto &targetUser = saveTask->m_targetUser;
if( !targetUser )
{
Log::Error( "CommitSaveTask: Target user not found." );
return false;
}
if( saveTask->m_saveType == CharacterSaveType::NEW_CHARACTER )
{
auto result = Database::Get().CreateNewCharacter( targetUser->m_accountId, saveTask->m_meta, saveTask->m_data );
if( !result )
{
Log::Error( "CommitSaveTask: Failed to create new character for account ID: {}", targetUser->m_accountId );
return false;
}
Log::Debug( "CommitSaveTask: New character created with ID {} for account ID: {}", result, targetUser->m_accountId );
targetUser->m_characterId = result;
}
else
{
if( 0 == saveTask->m_characterId )
{
Log::Error( "CommitSaveTask: Invalid character ID for save task." );
return false;
}
auto result = Database::Get().SaveCharacter( targetUser->m_accountId, targetUser->m_characterId, saveTask->m_meta, saveTask->m_data );
if( !result )
{
Log::Error( "CommitSaveTask: Failed to save character for account ID: {}", targetUser->m_accountId );
return false;
}
Log::Debug( "CommitSaveTask: Character saved for account ID: {}", targetUser->m_accountId );
}
}
catch( const std::exception &e )
{
Log::Error( "CommitSaveTask: Exception occurred while committing task for session ID [{}]: {}", sessionId, e.what() );
return false;
}
Log::Debug( "CommitSaveTask: Task for session ID [{}] committed successfully.", sessionId );
return true;
}
void CharacterSaveManager::RemoveSaveTask( const std::wstring &sessionId )
{
auto it = m_tasks.find( sessionId );
if( it != m_tasks.end() )
{
m_tasks.erase( it );
Log::Debug( "RemoveSaveTask: Task for session ID [{}] removed.", sessionId );
}
else
{
Log::Error( "RemoveSaveTask: Task for session ID [{}] not found.", sessionId );
}
}
sptr_character_save_task CharacterSaveManager::FindSaveTask( const std::wstring &sessionId )
{
auto it = m_tasks.find( sessionId );
if( it != m_tasks.end() )
{
return it->second;
}
else
{
Log::Error( "FindSaveTask: Task for session ID [{}] not found.", sessionId );
return nullptr;
}
}

76
Source/Game/ChatRoom.cpp Normal file
View File

@@ -0,0 +1,76 @@
#include "Game/ChatRoomSession.hpp"
#include "Game/RealmUser.hpp"
ChatRoomSession::ChatRoomSession()
{
m_type = RoomType::Public;
m_index = 0;
m_name.clear();
}
ChatRoomSession::~ChatRoomSession()
{
m_type = RoomType::Public;
m_index = 0;
m_name.clear();
}
bool ChatRoomSession::AddMember( sptr_user user )
{
if( !user )
return false;
for( const auto &member : m_members )
{
if( member.lock() == user )
return false; // User already in the room.
}
m_members.push_back( user );
return true;
}
bool ChatRoomSession::RemoveMember( sptr_user user )
{
if( !user )
return false;
auto it = std::remove_if( m_members.begin(), m_members.end(),
[ &user ]( const std::weak_ptr< RealmUser > &member )
{
return member.lock() == user;
} );
if( it == m_members.end() )
{
return false;
}
m_members.erase( it, m_members.end() );
return true;
}
bool ChatRoomSession::IsMember( sptr_user user )
{
if( !user )
return false;
for( const auto &member : m_members )
{
if( member.lock() == user )
return true;
}
return false;
}
bool ChatRoomSession::IsPublic() const
{
return m_type == RoomType::Public;
}
bool ChatRoomSession::IsPrivate() const
{
return m_type == RoomType::Private;
}

View File

@@ -0,0 +1,294 @@
#include "Game/ChatRoomManager.hpp"
#include "Game/RealmUser.hpp"
#include "Network/Event/NotifyRoomMessage.hpp"
#include "logging.hpp"
ChatRoomManager::ChatRoomManager()
{
m_chatSessionList.clear();
m_roomIndex = 0;
CreatePublicRooms();
}
ChatRoomManager::~ChatRoomManager()
{
}
void ChatRoomManager::CreatePublicRooms()
{
std::vector< std::wstring > RoomNames = {
L"Champions Reborn",
L"Adventurous",
L"Courageous",
L"Champion",
L"Legendary",
L"Epic"
};
for( const auto &name : RoomNames )
{
auto new_session = std::make_shared< ChatRoomSession >();
new_session->m_name = name;
new_session->m_type = ChatRoomSession::RoomType::Public;
new_session->m_index = m_roomIndex;
new_session->m_banner = L"";
m_chatSessionList[ m_roomIndex++ ] = new_session;
}
}
std::vector<sptr_chat_room_session> ChatRoomManager::GetPublicRoomList() const
{
std::vector< sptr_chat_room_session > result;
for( const auto &chatSession : m_chatSessionList )
{
if( chatSession.second->m_type == ChatRoomSession::RoomType::Public )
{
result.push_back( chatSession.second );
}
}
return result;
}
bool ChatRoomManager::JoinRoom( sptr_user user, const std::wstring &roomName )
{
if( roomName.empty() || !user )
return false;
auto chatSession = FindRoom( roomName );
if( !chatSession )
{
Log::Error( "Chat room [{}] not found", roomName );
return false;
}
if( !chatSession->AddMember( user ) )
{
Log::Error( "Failed to add user [{}] to chat room [{}]", user->m_username, roomName );
return false;
}
if( chatSession->m_type == ChatRoomSession::RoomType::Public )
{
user->m_publicRoomId = chatSession->m_index;
}
else
{
user->m_privateRoomId = chatSession->m_index;
SendMessageToRoom( roomName, L"", L"User '" + user->m_chatHandle + L"' has joined the room." );
}
Log::Info( "User [{}] joined chat room [{}]", user->m_username, roomName );
return true;
}
bool ChatRoomManager::LeaveRoom( sptr_user user, const std::wstring &roomName )
{
if( !user || roomName.empty() )
return false;
auto chatSession = FindRoom( roomName );
if( !chatSession )
{
Log::Error( "Chat room [{}] not found", roomName );
return false;
}
if( !chatSession->RemoveMember( user ) )
{
Log::Error( "Failed to remove user [{}] from chat room [{}]", user->m_username, roomName );
return false;
}
if( chatSession->m_type == ChatRoomSession::RoomType::Private )
{
if( chatSession->m_members.empty() && chatSession->m_moderators.empty() )
{
m_chatSessionList.erase( chatSession->m_index );
Log::Debug( "Private chat room [{}] deleted", roomName );
}
user->m_privateRoomId = -1;
}
else
{
user->m_publicRoomId = -1;
}
Log::Info( "User [{}] left chat room [{}]", user->m_username, roomName );
return true;
}
bool ChatRoomManager::LeaveRoom( sptr_user user, const int32_t roomId )
{
if( !user || roomId < 0 )
return false;
const auto chatSession = FindRoom( roomId );
if( !chatSession )
{
Log::Error( "Chat room with ID [{}] not found", roomId );
return false;
}
if( !chatSession->RemoveMember( user ) )
{
Log::Error( "Failed to remove user [{}] from chat room with ID [{}]", user->m_username, roomId );
return false;
}
if( chatSession->m_type == ChatRoomSession::RoomType::Private )
{
if( chatSession->m_members.empty() && chatSession->m_moderators.empty() )
{
m_chatSessionList.erase( roomId );
Log::Info( "Private chat room with ID [{}] deleted", roomId );
}
user->m_privateRoomId = -1;
}
else
{
user->m_publicRoomId = -1;
}
Log::Info( "User [{}] left chat room with ID [{}]", user->m_username, roomId );
return true;
}
void ChatRoomManager::OnDisconnectUser( sptr_user user )
{
if( !user ) return;
LeaveRoom( user, user->m_publicRoomId );
LeaveRoom( user, user->m_privateRoomId );
}
bool ChatRoomManager::CreateGameChatSession( sptr_user owner, std::wstring roomName )
{
for( const auto &chatSession : m_chatSessionList )
{
if( chatSession.second->m_name == roomName )
{
Log::Error( "Chat Room name is already in use! [{}]", roomName );
return false;
}
}
auto new_session = std::make_shared< ChatRoomSession >();
new_session->m_type = ChatRoomSession::RoomType::Private;
new_session->m_name = roomName;
new_session->m_owner = owner;
new_session->m_banner = L"";
new_session->m_index = m_roomIndex;
new_session->AddMember( owner );
owner->m_privateRoomId = m_roomIndex;
m_chatSessionList[ m_roomIndex++ ] = new_session;
return true;
}
bool ChatRoomManager::CloseGameChatSession( const std::wstring roomName )
{
if( roomName.empty() )
return false;
const auto it = std::find_if( m_chatSessionList.begin(), m_chatSessionList.end(),
[ &roomName ]( const auto &pair )
{
return pair.second->m_name == roomName;
} );
if( it == m_chatSessionList.end() )
{
Log::Error( "Chat room [{}] not found", roomName );
return false;
}
auto &chatSession = it->second;
if( chatSession->m_type != ChatRoomSession::RoomType::Private )
{
Log::Error( "Chat room [{}] is not a private room", roomName );
return false;
}
for( const auto &member : chatSession->m_members )
{
if( auto user = member.lock() )
{
user->m_privateRoomId = -1;
}
}
m_chatSessionList.erase( it );
Log::Info( "Chat room [{}] closed", roomName );
return true;
}
void ChatRoomManager::SendMessageToRoom( std::wstring roomName, std::wstring handle, std::wstring message )
{
if( roomName.empty() || message.empty() )
return;
auto chatSession = FindRoom( roomName );
if( !chatSession )
{
Log::Error( "Chat room [{}] not found", roomName );
return;
}
NotifyRoomMessage notifyMessage( roomName, handle, message );
for( const auto &m : chatSession->m_members )
{
if( auto member = m.lock() )
{
member->sock->send( notifyMessage );
}
}
}
sptr_chat_room_session ChatRoomManager::FindRoom( const std::wstring &gameName )
{
if( gameName.empty() )
return nullptr;
for( const auto &chatSession : m_chatSessionList )
{
if( chatSession.second->m_name == gameName )
{
return chatSession.second;
}
}
return nullptr;
}
sptr_chat_room_session ChatRoomManager::FindRoom( const int32_t roomId )
{
if( roomId < 0 )
return nullptr;
auto it = m_chatSessionList.find( roomId );
if( it != m_chatSessionList.end() )
{
return it->second;
}
Log::Error( "Chat room with ID [{}] not found", roomId );
return nullptr;
}

186
Source/Game/GameSession.cpp Normal file
View File

@@ -0,0 +1,186 @@
#include "Game/GameSession.hpp"
#include "Game/RealmUser.hpp"
#include "logging.hpp"
GameSession::GameSession( uint32_t index ) : m_gameId( index )
{
m_members.fill( std::weak_ptr< RealmUser >() );
m_type = GameType::Public;
m_state = GameState::NotReady;
m_currentPlayers = 0;
m_maximumPlayers = 4;
m_difficulty = 0;
m_gameMode = 0;
m_mission = 0;
m_unknown = 0;
m_networkSave = 0;
m_hostNatPort = 0;
m_hostLocalPort = 0;
m_hostLocalAddr.clear();
m_hostExternalAddr.clear();
m_gameName.clear();
m_ownerName.clear();
m_gameData.clear();
m_description.clear();
}
GameSession::~GameSession()
{
m_members.fill( std::weak_ptr< RealmUser >() );
m_gameId = 0;
m_type = GameType::Public;
m_state = GameState::NotReady;
m_currentPlayers = 0;
m_maximumPlayers = 0;
m_difficulty = 0;
m_gameMode = 0;
m_mission = 0;
m_unknown = 0;
m_networkSave = 0;
m_hostNatPort = 0;
m_hostLocalAddr.clear();
m_hostExternalAddr.clear();
m_gameName.clear();
m_ownerName.clear();
m_gameData.clear();
m_description.clear();
}
bool GameSession::IsJoinable( sptr_user user ) const
{
if( user )
{
for( const auto &m : m_members )
{
if( m.expired() )
continue;
const auto &member = m.lock();
if( member == user )
{
return false;
}
}
}
return ( m_state == GameState::Open && m_currentPlayers < m_maximumPlayers );
}
sptr_user GameSession::GetOwner() const
{
if( !m_members[ 0 ].expired() )
{
return m_members[ 0 ].lock();
}
return nullptr;
}
sptr_user GameSession::GetMember( int32_t index ) const
{
if( index < 0 || index >= static_cast< int32_t >( m_members.size() ) )
return nullptr;
if( !m_members[ index ].expired() )
{
return m_members[ index ].lock();
}
return nullptr;
}
sptr_user GameSession::GetMemberBySessionId( const std::wstring &sessionId ) const
{
for( const auto &m : m_members )
{
if( m.expired() )
continue;
const auto &member = m.lock();
if( member->m_sessionId == sessionId )
{
return member;
}
}
return nullptr;
}
std::vector<sptr_user> GameSession::GetMembers() const
{
std::vector< sptr_user > members;
for( const auto &m : m_members )
{
if( !m.expired() )
{
members.push_back( m.lock() );
}
}
return members;
}
bool GameSession::AddMember( sptr_user user )
{
if( !user ) return false;
int8_t freeIndex = -1;
for( int8_t i = 0; i < static_cast< int8_t >( m_members.size() ); ++i )
{
auto memberPtr = m_members[ i ].lock();
if( memberPtr )
{
if( memberPtr->m_sessionId == user->m_sessionId )
return false;
}
else if( freeIndex == -1 )
{
freeIndex = i;
}
}
if( freeIndex == -1 )
{
Log::Error( "Game session is full! [{}]", m_gameName );
return false;
}
user->m_gameId = m_gameId;
m_members[ freeIndex ] = user;
m_currentPlayers++;
Log::Info( "Added user [{}] to game session [{}] at index {}",
user->m_username, m_gameName, freeIndex );
return true;
}
bool GameSession::RemoveMember( sptr_user user )
{
if( !user ) return false;
for( int8_t i = 0; i < static_cast< int8_t >( m_members.size() ); ++i )
{
auto memberPtr = m_members[ i ].lock();
if( memberPtr && memberPtr == user )
{
user->m_gameId = -1;
m_members[ i ].reset();
break;
}
}
m_currentPlayers--;
Log::Info( "Removed user [{}] from game session [{}]", user->m_username, m_gameName );
return true;
}

View File

@@ -0,0 +1,422 @@
#include "Game/GameSessionManager.hpp"
#include "Game/RealmUser.hpp"
#include "Network/Event/NotifyGameDiscovered.hpp"
#include "Network/Event/NotifyClientDiscovered.hpp"
#include "Network/Event/NotifyClientDiscovered_RTA.hpp"
#include "Network/Event/NotifyClientRequestConnect.hpp"
#include "Network/Event/NotifyClientRequestConnect_RTA.hpp"
#include "logging.hpp"
GameSessionManager::GameSessionManager()
{
m_uniqueGameIndex = 0;
m_gameSessionList[ 0 ].clear();
m_gameSessionList[ 1 ].clear();
}
GameSessionManager::~GameSessionManager()
{
}
void GameSessionManager::OnDisconnectUser( sptr_user user )
{
if( !user || user->m_gameId < 0 )
return;
const auto gameId = user->m_gameId;
const auto gameType = user->m_gameType;
auto session = FindGame( gameId, gameType );
if( !session )
return;
const auto owner = session->GetOwner();
if( !owner )
{
Log::Error( "Game session owner not found! [{}]", gameId );
ForceTerminateGame( gameId, gameType );
return;
}
if( owner->m_sessionId == user->m_sessionId )
{
Log::Info( "Game session owner disconnected! [{}]", gameId );
ForceTerminateGame( gameId, gameType );
}
}
bool GameSessionManager::CreateGameSession_CON( sptr_user user,
const std::wstring gameInfo,
const std::wstring name,
const std::wstring stage,
bool isPrivateGame )
{
if( name.empty() )
{
Log::Error( "Invalid parameters for creating game session!" );
return false;
}
auto new_session = std::make_shared< GameSession >( m_uniqueGameIndex++ );
if( isPrivateGame )
{
new_session->m_type = GameSession::GameType::Private;
new_session->m_gameName = name;
}
else
{
new_session->m_type = GameSession::GameType::Public;
new_session->m_gameName = name + L" [" + stage + L"]";
}
user->m_isHost = true;
user->m_gameId = new_session->m_gameId;
user->m_discoveryAddr = "";
user->m_discoveryPort = 0;
new_session->AddMember( user );
std::lock_guard< std::mutex > lock( m_dataMutex );
m_gameSessionList[ RealmGameType::CHAMPIONS_OF_NORRATH ].push_back( new_session );
return true;
}
bool GameSessionManager::CreateGameSession_RTA(
sptr_user user,
const std::wstring gameInfo,
const std::wstring name,
const std::array< int8_t, 5 > &attributes,
const bool isPrivateGame )
{
if( nullptr != FindGame( name, RealmGameType::RETURN_TO_ARMS ) )
{
Log::Error( "Game name is already in use! [{}]", name );
return false;
}
auto new_session = std::make_shared< GameSession >( m_uniqueGameIndex++ );
if( isPrivateGame )
{
new_session->m_type = GameSession::GameType::Private;
}
else
{
new_session->m_type = GameSession::GameType::Public;
new_session->m_gameData = Util::WideToUTF8( gameInfo );
new_session->m_difficulty = attributes[ 0 ];
new_session->m_gameMode = attributes[ 1 ];
new_session->m_unknown = attributes[ 2 ];
new_session->m_mission = attributes[ 3 ];
new_session->m_networkSave = attributes[ 4 ];
}
new_session->m_gameName = name;
user->m_isHost = true;
user->m_gameId = new_session->m_gameId;
user->m_discoveryAddr = "";
user->m_discoveryPort = 0;
new_session->AddMember( user );
std::lock_guard< std::mutex > lock( m_dataMutex );
m_gameSessionList[ RealmGameType::RETURN_TO_ARMS ].push_back( new_session );
return true;
}
bool GameSessionManager::ForceTerminateGame( int32_t gameId, RealmGameType clientType )
{
if( gameId < 0 )
{
return false;
}
std::lock_guard< std::mutex > lock( m_dataMutex );
const auto &gameList = m_gameSessionList[ clientType ];
const auto it = std::find_if( gameList.begin(), gameList.end(), [ &gameId ]( sptr_game_session gameSession )
{
return gameSession->m_gameId == gameId;
} );
if( it != m_gameSessionList[ clientType ].end() )
{
m_gameSessionList[ clientType ].erase( it );
return true;
}
return false;
}
sptr_game_session GameSessionManager::FindGame( const int32_t gameId, const RealmGameType gameType )
{
if( gameId < 0 ) return nullptr;
std::lock_guard< std::mutex > lock( m_dataMutex );
for( auto &gameSession : m_gameSessionList[ gameType ] )
{
if( gameSession->m_gameId == gameId )
{
return gameSession;
}
}
return nullptr;
}
sptr_game_session GameSessionManager::FindGame( const std::wstring &gameName, const RealmGameType gameType )
{
if( gameName.empty() ) return nullptr;
std::lock_guard< std::mutex > lock( m_dataMutex );
for( auto &gameSession : m_gameSessionList[ gameType ] )
{
if( gameSession->m_gameName == gameName )
{
return gameSession;
}
}
return nullptr;
}
bool GameSessionManager::RequestOpen( sptr_user user )
{
auto gameId = user->m_gameId;
auto gameType = user->m_gameType;
auto session = FindGame( gameId, gameType );
if( session == nullptr )
{
Log::Error( "Game session not found! [{}]", gameId );
return false;
}
if( session->m_state == GameSession::GameState::Open )
{
return false;
}
if( user->m_discoveryAddr.empty() )
{
Log::Error( "User discovery address is empty! [{}]", gameId );
return false;
}
session->m_hostLocalAddr = user->m_localAddr;
session->m_hostLocalPort = user->m_localPort;
session->m_hostExternalAddr = user->m_discoveryAddr;
session->m_hostNatPort = user->m_discoveryPort;
session->m_state = GameSession::GameState::Open;
// Tell the host its own address.
user->sock->send( NotifyGameDiscovered( user ) );
Log::Info( "Game Session [{}] Discoverable on {}", gameId, user->m_discoveryAddr );
return true;
}
bool GameSessionManager::RequestCancel( sptr_user user )
{
if( !user || user->m_gameId < 0 )
return false;
std::lock_guard<std::mutex> lock( m_dataMutex );
const auto gameId = user->m_gameId;
const auto gameType = user->m_gameType;
auto &gameList = m_gameSessionList[ gameType ];
const auto it = std::find_if( gameList.begin(), gameList.end(),
[ gameId ]( const sptr_game_session &gameSession )
{
return gameSession->m_gameId == gameId;
} );
if( it == gameList.end() )
return false;
const auto &session = *it;
if( false == session->RemoveMember( user ) )
{
Log::Error( "Failed to remove user [{}] from game session [{}]", user->m_username, gameId );
}
if( session->m_currentPlayers <= 0 )
{
Log::Info( "Game session [{}] is empty, removing it", gameId );
gameList.erase( it );
}
else
{
Log::Info( "User [{}] left game session [{}], remaining players: {}",
user->m_username, gameId, session->m_currentPlayers );
}
return true;
}
bool GameSessionManager::RequestJoin( sptr_user join_user )
{
const auto gameId = join_user->m_gameId;
const auto gameType = join_user->m_gameType;
auto session = FindGame( gameId, gameType );
if( session == nullptr )
{
Log::Error( "Game session not found! [{}]", gameId );
return false;
}
if( session->m_state != GameSession::GameState::Open )
{
Log::Error( "Game session not open! [{}]", gameId );
return false;
}
auto host_user = session->GetOwner();
if( host_user == nullptr )
{
Log::Error( "Host not found! [{}]", gameId );
ForceTerminateGame( gameId, gameType );
return false;
}
if( host_user->m_discoveryAddr.empty() )
{
Log::Error( "User discovery address is empty! [{}]", gameId );
ForceTerminateGame( gameId, gameType );
return false;
}
if( host_user->m_gameType == RealmGameType::CHAMPIONS_OF_NORRATH )
{
ProcessJoinNorrath( join_user, host_user );
}
else
{
ProcessJoinArms( join_user, host_user );
}
Log::Info( "User [{}] Joining game session... [{}]", join_user->m_sessionId, gameId );
return true;
}
// NOTE:
// Request Start seems to be for RTA only.
// CON will disconnect from the server at start time.
bool GameSessionManager::RequestStart( sptr_user user )
{
const auto gameId = user->m_gameId;
const auto gameType = user->m_gameType;
auto session = FindGame( gameId, gameType );
if( session == nullptr )
{
Log::Error( "Game session not found! [{}]", gameId );
return false;
}
std::lock_guard<std::mutex> lock( m_dataMutex );
session->m_state = GameSession::GameState::Started;
// Remove the game from the list.
auto &gameList = m_gameSessionList[ gameType ];
const auto it = std::find_if( gameList.begin(), gameList.end(),
[ gameId ]( const sptr_game_session &gameSession )
{
return gameSession->m_gameId == gameId;
} );
if( it != gameList.end() )
{
gameList.erase( it );
}
Log::Info( "Game session [{}] started", gameId );
return true;
}
std::vector<sptr_game_session> GameSessionManager::GetAvailableGameSessionList( const RealmGameType gameType ) const
{
std::lock_guard<std::mutex> lock( m_dataMutex );
std::vector<sptr_game_session> list;
for( const auto &game : m_gameSessionList[ gameType ] )
{
if( game->m_type == GameSession::GameType::Public &&
game->m_state == GameSession::GameState::Open )
{
list.push_back( game );
}
}
return list;
}
std::vector<sptr_game_session> GameSessionManager::GetPublicGameSessionList( const RealmGameType gameType ) const
{
std::lock_guard<std::mutex> lock( m_dataMutex );
std::vector<sptr_game_session> list;
for( const auto &game : m_gameSessionList[ gameType ] )
{
if( game->m_type == GameSession::GameType::Public )
list.push_back( game );
}
return list;
}
std::vector<sptr_game_session> GameSessionManager::GetPrivateGameSessionList( const RealmGameType gameType ) const
{
std::lock_guard<std::mutex> lock( m_dataMutex );
std::vector<sptr_game_session> list;
for( const auto &game : m_gameSessionList[ gameType ] )
{
if( game->m_type == GameSession::GameType::Private )
list.push_back( game );
}
return list;
}
void GameSessionManager::ProcessJoinNorrath( sptr_user join, sptr_user host )
{
// Tell the joining user its own address
join->sock->send( NotifyClientDiscovered( join ) );
// Tell the host the joining user's address.
host->sock->send( NotifyClientRequestConnect( join ) );
}
void GameSessionManager::ProcessJoinArms( sptr_user join, sptr_user host )
{
Log::Debug("Join User IPs : [{}:{}] -> [{}:{}]",
join->m_localAddr, join->m_localPort, join->m_discoveryAddr, join->m_discoveryPort );
Log::Debug("Host User IPs : [{}:{}] -> [{}:{}]",
host->m_localAddr, host->m_localPort, host->m_discoveryAddr, host->m_discoveryPort );
// Tell the joining user its own address
join->sock->send( NotifyClientDiscovered_RTA( join ) );
// Tell the host the joining user's address.
host->sock->send( NotifyClientRequestConnect_RTA( join ) );
}

View File

@@ -0,0 +1,659 @@
#include "Game/RealmCharacter.hpp"
#include "Game/CharacterSaveTask.hpp"
#include "Common/ByteBufferReader.hpp"
#include "Common/RLEZ.hpp"
#include "logging.hpp"
RealmCharacter::RealmCharacter()
{
m_characterId = 0;
m_data.clear();
Initialize();
}
RealmCharacter::RealmCharacter( const int32_t character_id, const CharacterSlotData meta, const std::vector<uint8_t> data )
{
m_characterId = character_id;
m_data = data;
m_metaData = meta;
Initialize();
}
RealmCharacter::~RealmCharacter()
{
}
void RealmCharacter::Initialize()
{
name = "";
for( auto &val : unknown_000 )
{
val = 0;
}
unknown_str = "";
for( auto &val : unknown_004 )
{
val = 0;
}
character_class = CharacterClass::WARRIOR;
character_race = CharacterRace::BARBARIAN_M;
current_level = 0;
pending_level = 0;
unknown_009 = 0;
experience = 0;
std::memset( unknown_010, 0, sizeof( unknown_010 ) );
std::memset( stats, 0, sizeof( stats ) );
unknown_016 = 0;
current_hp = 0.0f;
maximum_hp = 0.0f;
unknown_017 = 0;
unknown_018 = 0;
unknown_019 = 0;
current_mana = 0.0f;
maximum_mana = 0.0f;
unknown_020 = 0;
attack_power = 0;
minimum_damage = 0;
maximum_damage = 0;
unknown_021 = 0;
unknown_022 = 0;
unknown_023 = 0;
unknown_024 = 0;
unknown_025 = 0;
unknown_026 = 0;
current_gold = 0;
current_skill_points = 0;
current_ability_points = 0;
has_spent_remaining_points = 0;
unknown_029 = 0;
unknown_030 = 0;
unknown_031 = 0;
unknown_032 = 0;
unknown_033 = 0;
unknown_034 = 0;
unknown_035 = 0;
unknown_036 = 0;
unknown_037 = 0;
unknown_038 = 0;
unknown_039 = 0;
unknown_040 = 0;
weight = 0.0f;
max_weight = 0.0f;
unknown_041 = 0;
unknown_042 = 0;
item_data.fill( ItemData() );
num_armor_item = 0;
unknown_043 = 0;
armor_item_data.fill( ItemData() );
num_weapon_item = 0;
unknown_044 = 0;
weapon_item_data.fill( ItemData() );
num_consumable_item = 0;
unknown_045 = 0;
unknown_046.fill( 0 );
quest_string.fill( s_quest() );
num_quests = 0;
unknown_048 = 0;
unknown_049 = 0;
unknown_050 = 0;
unknown_051 = 0;
unknown_052 = 0;
unknown_053 = 0;
unknown_054 = 0;
equipment.fill( 0 );
newStyle.fill( s_new_style() );
unknown_075 = 0;
unknown_076 = 0.0f;
unknown_077 = 0;
unknown_078 = 0;
unknown_079 = 0;
unknown_080 = 0;
unknown_081 = 0;
unknown_082 = 0;
unknown_083 = 0;
unknown_084 = 0;
unknown_085 = 0;
skill_slot[ 0 ] = 0;
skill_slot[ 1 ] = 0;
unknown_087 = 0;
unknown_088 = 0;
attackData.fill( AttackData() );
skills.fill( s_skill() );
unknown_091 = 0;
mission_progress.fill( s_difficulty_progress() );
mission_medals.fill( 0 );
evil_bitflag = 0;
good_bitflag = 0;
unknown_101 = 0;
movement_speed = 0.0f;
unknown_102 = 0;
unknown_103 = 0;
unknown_104 = 0;
unknown_105 = 0;
unknown_106 = 0;
unknown_107 = 0;
unknown_108 = 0;
unknown_109 = 0;
unknown_110 = 0;
}
const std::vector< uint8_t > RealmCharacter::Serialize() const
{
auto writer = ByteBuffer( CHARACTER_DATA_SIZE );
/*writer.write_sz_utf8(name, 32);
for( const auto &val : unknown_000 )
{
writer.write_u8( val );
}
writer.write_sz_utf8( unknown_str, 32 );
for( const auto &val : unknown_004 )
{
writer.write_i32( val );
}
writer.write_i32( static_cast< int32_t >( character_class ) );
writer.write_i32( static_cast< int32_t >( character_race ) );
writer.write_u8( current_level );
writer.write_u8( pending_level );
writer.write_u16( unknown_009 );
writer.write_i32( experience );
for( const auto &val : unknown_010 )
{
writer.write_i32( val );
}
for( const auto &stat : stats )
{
writer.write( stat );
}
writer.write_i32( unknown_016 );
writer.write_f32( current_hp );
writer.write_f32( maximum_hp );
writer.write_i32( unknown_017 );
writer.write_i32( unknown_018 );
writer.write_i32( unknown_019 );
writer.write_f32( current_mana );
writer.write_f32( maximum_mana );
writer.write_i32( unknown_020 );
writer.write_i32( attack_power );
writer.write_i32( minimum_damage );
writer.write_i32( maximum_damage );
writer.write_i32( unknown_021 );
writer.write_i32( unknown_022 );
writer.write_u8( unknown_023 );
writer.write_u8( unknown_024 );
writer.write_u8( unknown_025 );
writer.write_u8( unknown_026 );
writer.write_i32( current_gold );
writer.write_i32( current_skill_points );
writer.write_i16( current_ability_points );
writer.write_i16( has_spent_remaining_points );
writer.write_i32( unknown_029 );
writer.write_i32( unknown_030 );
writer.write_i32( unknown_031 );
writer.write_i32( unknown_032 );
writer.write_i32( unknown_033 );
writer.write_i32( unknown_034 );
writer.write_i32( unknown_035 );
writer.write_i32( unknown_036 );
writer.write_i32( unknown_037 );
writer.write_i32( unknown_038 );
writer.write_i32( unknown_039 );
writer.write_i32( unknown_040 );
writer.write_f32( weight );
writer.write_f32( max_weight );
writer.write_i32( unknown_041 );
writer.write_i32( unknown_042 );
for( const auto &item : item_data )
{
writer.write( item );
}
writer.write_i32( num_armor_item );
writer.write_i32( unknown_043 );
for( const auto &item : armor_item_data )
{
writer.write( item );
}
writer.write_i32( num_weapon_item );
writer.write_i32( unknown_044 );
for( const auto &item : weapon_item_data )
{
writer.write( item );
}
writer.write_i32( num_consumable_item );
writer.write_i32( unknown_045 );
writer.forward( 2696 );
for( const auto &quest : quest_string )
{
writer.write_sz_utf8( quest.name, 32 );
writer.write_sz_utf8( quest.description, 32 );
}
writer.write_i32( num_quests );
writer.write_i32( unknown_048 );
writer.write_i32( unknown_049 );
writer.write_i32( unknown_050 );
writer.write_i32( unknown_051 );
writer.write_i32( unknown_052 );
writer.write_i32( unknown_053 );
writer.write_i32( unknown_054 );
for( const auto &slot : equipment )
{
writer.write_i32( slot );
}
for( const auto &style : newStyle )
{
writer.write< s_new_style >( style );
}
writer.write_i32( unknown_075 );
writer.write_f32( unknown_076 );
writer.write_i32( unknown_077 );
writer.write_i32( unknown_078 );
writer.write_i32( unknown_079 );
writer.write_i32( unknown_080 );
writer.write_i32( unknown_081 );
writer.write_i32( unknown_082 );
writer.write_i32( unknown_083 );
writer.write_i32( unknown_084 );
writer.write_i32( unknown_085 );
for( const auto &skill : skill_slot )
{
writer.write_u8( skill );
}
writer.write_u8( unknown_087 );
writer.write_u8( unknown_088 );
for( const auto &attack : attackData )
{
writer.write< AttackData >( attack );
}
for( const auto &skill : skills )
{
writer.write< s_skill >( skill );
}
writer.write_i32( unknown_091 );
for( const auto &progress : mission_progress )
{
writer.write< s_difficulty_progress >( progress );
}
for( const auto &medal : mission_medals )
{
writer.write_u8( medal );
}
writer.write_u8( evil_bitflag );
writer.write_u8( good_bitflag );
writer.write_i32( unknown_101 );
writer.write_f32( movement_speed );
writer.write_u8( unknown_102 );
writer.write_u8( unknown_103 );
writer.write_u8( unknown_104 );
writer.write_u8( unknown_105 );
writer.write_u8( unknown_106 );
writer.write_u8( unknown_107 );
writer.write_u8( unknown_108 );
writer.write_u8( unknown_109 );
writer.write_i32( unknown_110 );*/
return writer.get_buffer();
}
void RealmCharacter::Deserialize( const std::vector<uint8_t> &data )
{
ByteBufferReader reader( data );
/*try
{
name = reader.readString( 32 );
for( auto &val : unknown_000 )
{
val = reader.read< uint8_t >();
}
unknown_str = reader.readString( 32 );
for( auto &val : unknown_004 )
{
val = reader.read< int32_t >();
}
character_class = static_cast< CharacterClass >( reader.read< int32_t >() );
character_race = static_cast< CharacterRace >( reader.read< int32_t >() );
current_level = reader.read< uint8_t >();
pending_level = reader.read< uint8_t >();
unknown_009 = reader.read< uint16_t >();
experience = reader.read< int32_t >();
for( auto &val : unknown_010 )
{
val = reader.read< int32_t >();
}
for( auto &stat : stats )
{
stat = reader.read< s_stats >();
}
unknown_016 = reader.read< int32_t >();
current_hp = reader.read< float_t >();
maximum_hp = reader.read< float_t >();
unknown_017 = reader.read< int32_t >();
unknown_018 = reader.read< int32_t >();
unknown_019 = reader.read< int32_t >();
current_mana = reader.read< float_t >();
maximum_mana = reader.read< float_t >();
unknown_020 = reader.read< int32_t >();
attack_power = reader.read< int32_t >();
minimum_damage = reader.read< int32_t >();
maximum_damage = reader.read< int32_t >();
unknown_021 = reader.read< int32_t >();
unknown_022 = reader.read< int32_t >();
unknown_023 = reader.read< uint8_t >();
unknown_024 = reader.read< uint8_t >();
unknown_025 = reader.read< uint8_t >();
unknown_026 = reader.read< uint8_t >();
current_gold = reader.read< int32_t >();
current_skill_points = reader.read< int32_t >();
current_ability_points = reader.read< int16_t >();
has_spent_remaining_points = reader.read< int16_t >();
//
unknown_029 = reader.read< int32_t >();
unknown_030 = reader.read< int32_t >();
unknown_031 = reader.read< int32_t >();
unknown_032 = reader.read< int32_t >();
//
unknown_033 = reader.read< int32_t >();
unknown_034 = reader.read< int32_t >();
unknown_035 = reader.read< int32_t >();
unknown_036 = reader.read< int32_t >();
//
unknown_037 = reader.read< int32_t >();
unknown_038 = reader.read< int32_t >();
unknown_039 = reader.read< int32_t >();
unknown_040 = reader.read< int32_t >();
weight = reader.read< float_t >();
max_weight = reader.read< float_t >();
unknown_041 = reader.read< int32_t >();
unknown_042 = reader.read< int32_t >();
item_data = reader.readArray< ItemData, 64>();
num_armor_item = reader.read< int32_t >();
unknown_043 = reader.read< int32_t >();
armor_item_data = reader.readArray< ItemData, 64>();
num_weapon_item = reader.read< int32_t >();
unknown_044 = reader.read< int32_t >();
weapon_item_data = reader.readArray< ItemData, 64>();
num_consumable_item = reader.read< int32_t >();
unknown_045 = reader.read< int32_t >();
unknown_046.fill( 0x00 ); // Fill with zeros, size is 2696 bytes
for( auto &quest : quest_string )
{
quest = reader.read< s_quest >();
}
num_quests = reader.read< int32_t >();
unknown_048 = reader.read< int32_t >();
unknown_049 = reader.read< int32_t >();
unknown_050 = reader.read< int32_t >();
unknown_051 = reader.read< int32_t >();
unknown_052 = reader.read< int32_t >();
unknown_053 = reader.read< int32_t >();
unknown_054 = reader.read< int32_t >();
for( auto &slot : equipment )
{
slot = reader.read< int32_t >();
}
for( auto &style : newStyle )
{
style = reader.read< s_new_style >();
}
unknown_075 = reader.read< int32_t >();
unknown_076 = reader.read< float_t >();
unknown_077 = reader.read< int32_t >();
unknown_078 = reader.read< int32_t >();
unknown_079 = reader.read< int32_t >();
unknown_080 = reader.read< int32_t >();
unknown_081 = reader.read< int32_t >();
unknown_082 = reader.read< int32_t >();
unknown_083 = reader.read< int32_t >();
unknown_084 = reader.read< int32_t >();
unknown_085 = reader.read< int32_t >();
for( auto &skill : skill_slot )
{
skill = reader.read< uint8_t >();
}
unknown_087 = reader.read< uint8_t >();
unknown_088 = reader.read< uint8_t >();
for( auto &attack : attackData )
{
attack = reader.read< AttackData >();
}
for( auto &skill : skills )
{
skill.skill_id = reader.read< int16_t >();
skill.skill_level = reader.read< int16_t >();
}
unknown_091 = reader.read< int32_t >();
for( auto &val : mission_progress )
{
val = reader.read< s_difficulty_progress >();
}
for( auto &val : mission_medals )
{
val = reader.read< uint8_t >();
}
evil_bitflag = reader.read< uint8_t >();
good_bitflag = reader.read< uint8_t >();
unknown_101 = reader.read< int32_t >();
movement_speed = reader.read< float_t >();
unknown_102 = reader.read< uint8_t >();
unknown_103 = reader.read< uint8_t >();
unknown_104 = reader.read< uint8_t >();
unknown_105 = reader.read< uint8_t >();
unknown_106 = reader.read< uint8_t >();
unknown_107 = reader.read< uint8_t >();
unknown_108 = reader.read< uint8_t >();
unknown_109 = reader.read< uint8_t >();
unknown_110 = reader.read< int32_t >();
}
catch( const std::out_of_range &e )
{
Log::Error( "Failed to deserialize RealmCharacter: %s", e.what() );
return;
}*/
}
bool RealmCharacter::ValidateData()
{
if( m_data.empty() || m_data.size() < CHARACTER_DATA_SIZE )
{
Log::Error( "Character data is invalid or too small!" );
return false;
}
if( current_level < 1 || current_level > 80 )
{
Log::Error( "Invalid character level: %d", current_level );
return false;
}
if( current_hp < 1.0f || current_hp > maximum_hp )
{
Log::Error( "Invalid HP values: current %f, maximum %f", current_hp, maximum_hp );
return false;
}
if( current_mana < 0.0f || current_mana > maximum_mana )
{
Log::Error( "Invalid mana values: current %f, maximum %f", current_mana, maximum_mana );
return false;
}
if( weight < 0.0f || weight > max_weight )
{
Log::Error( "Invalid weight values: current %f, maximum %f", weight, max_weight );
return false;
}
if( movement_speed <= 0.0f )
{
Log::Error( "Invalid movement speed: %f", movement_speed );
return false;
}
for( const auto &mission : mission_progress )
{
if( mission.mission_na < 0 || mission.mission_na > 3 )
{
Log::Error( "Invalid mission NA value: %d", mission.mission_na );
return false;
}
if( mission.mission_Disease < 0 || mission.mission_Disease > 3 )
{
Log::Error( "Invalid mission Disease value: %d", mission.mission_Disease );
return false;
}
if( mission.mission_Fear < 0 || mission.mission_Fear > 3 )
{
Log::Error( "Invalid mission Fear value: %d", mission.mission_Fear );
return false;
}
}
return true;
}
CharacterSlotData RealmCharacter::GetMetaData() const
{
return m_metaData;
}
void RealmCharacter::SetMetaData( sptr_byte_stream stream )
{
m_metaData.Deserialize( stream );
}
std::vector<uint8_t> RealmCharacter::Unpack()
{
std::vector< uint8_t > output;
size_t read = 4;
while( read < m_data.size() )
{
uint8_t byte = m_data[ read++ ];
output.push_back( byte );
if( byte == 0x00 )
{
if( read >= m_data.size() )
{
break;
}
uint8_t count = m_data[ read++ ];
output.insert( output.end(), count, 0x00 );
}
}
return output;
}

View File

@@ -0,0 +1,93 @@
#include "Game/RealmCharacterMetaKV.hpp"
#include "Common/ByteBufferReader.hpp"
#include "Common/ByteStream.hpp"
#include "Common/Utility.hpp"
#include "logging.hpp"
CharacterSlotData::CharacterSlotData(const std::vector<uint8_t>& data)
{
if( !data.empty() )
{
Deserialize(data);
}
else
{
m_metaData.clear();
}
}
void CharacterSlotData::Deserialize(const std::vector< uint8_t >& data)
{
ByteBuffer reader(data);
auto numberOfKeys = reader.read_i32();
if( !Util::IsInRange( numberOfKeys, 0, 4 ) )
{
m_metaData.clear();
return;
}
m_metaData.clear();
m_metaData.reserve( numberOfKeys );
for( auto i = 0; i < numberOfKeys; ++i )
{
std::wstring key = reader.read_utf16();
m_metaData.emplace_back( key, L"" );
}
auto numberOfValues = reader.read_i32();
for( auto &pair : m_metaData )
{
pair.second = reader.read_utf16();
}
}
void CharacterSlotData::Deserialize(const sptr_byte_stream stream)
{
auto numberOfKeys = stream->read_i32();
if (!Util::IsInRange(numberOfKeys, 0, 4))
{
m_metaData.clear();
return;
}
m_metaData.clear();
m_metaData.reserve(numberOfKeys);
for (auto i = 0; i < numberOfKeys; ++i)
{
std::wstring key = stream->read_utf16();
m_metaData.emplace_back(key, L"");
}
auto numberOfValues = stream->read_i32();
for (auto& pair : m_metaData)
{
pair.second = stream->read_utf16();
}
}
std::vector< uint8_t > CharacterSlotData::Serialize() const
{
ByteBuffer stream;
// Number of keys
stream.write_u32( static_cast< int32_t >( m_metaData.size() ) );
for( const auto &pair : m_metaData )
{
stream.write_utf16( pair.first );
}
// Number of values
stream.write_u32( static_cast< uint32_t >( m_metaData.size() ) );
for( const auto &pair : m_metaData )
{
stream.write_utf16( pair.second );
}
return stream.m_buffer;
}

View File

@@ -0,0 +1,105 @@
#include "Game/CharacterSaveTask.hpp"
#include "Game/RealmCharacter.hpp"
#include "Common/ByteBufferReader.hpp"
#include "logging.hpp"
CharacterSaveTask::CharacterSaveTask( CharacterSaveType Type, uint32_t characterId )
{
m_saveType = Type;
m_ownerUser.reset();
m_targetUser.reset();
m_characterId = characterId;
m_writePosition = 0;
m_data.reserve(4096);
}
CharacterSaveTask::~CharacterSaveTask()
{
m_ownerUser.reset();
m_targetUser.reset();
m_data.clear();
}
void CharacterSaveTask::SetMetaData( const CharacterSlotData &metaData )
{
this->m_meta = metaData;
}
bool CharacterSaveTask::AppendData( const std::vector< uint8_t > &data )
{
if( data.empty() )
{
Log::Error( "AppendTailData called with empty data!" );
return false;
}
m_data.insert( m_data.end(), data.begin(), data.end() );
return true;
}
bool CharacterSaveTask::Validate()
{
if( m_data.empty() )
{
Log::Error( "EndUpdate called with empty pending data!" );
return false;
}
if( m_meta.empty() )
{
Log::Error( "EndUpdate called with empty metadata!" );
return false;
}
// Verify the size of the decompressed buffer
size_t read = 4;
size_t decompressedSize = 0;
size_t lastUsedByte = read;
while( read < m_data.size() )
{
uint8_t byte = m_data[ read++ ];
decompressedSize += 1;
lastUsedByte = read;
if( byte == 0x00 )
{
if( read >= m_data.size() )
{
Log::Error( "Unexpected end of data during decompression" );
return false;
}
uint8_t count = m_data[ read++ ];
decompressedSize += count;
lastUsedByte = read;
}
if( decompressedSize >= CHARACTER_DATA_SIZE )
{
break;
}
}
if( decompressedSize < CHARACTER_DATA_SIZE )
{
Log::Error( "Failed to decompress pending character data. More data needed!" );
return false;
}
// TODO: Better error checking.
// Trim Garbage from the compressed buffer.
m_data.resize( lastUsedByte );
return true;
}
const std::vector<uint8_t> &CharacterSaveTask::GetData() const
{
return m_data;
}

50
Source/Game/RealmUser.cpp Normal file
View File

@@ -0,0 +1,50 @@
#include "Game/RealmUser.hpp"
#include "Game/RealmCharacter.hpp"
RealmUser::RealmUser()
{
sock = nullptr;
m_gameType = RealmGameType::CHAMPIONS_OF_NORRATH;
m_accountId = -1;
m_sessionId = L"";
m_username = L"";
m_characterId = 0;
m_localAddr = "";
m_discoveryAddr = "";
m_discoveryPort = 0;
m_isLoggedIn = false;
m_isHost = false;
m_gameId = -1;
m_publicRoomId = -1;
m_privateRoomId = -1;
}
RealmUser::~RealmUser()
{
if( sock )
{
sock->flag.disconnected_wait = true;
sock.reset();
}
m_gameType = RealmGameType::CHAMPIONS_OF_NORRATH;
m_accountId = 0;
m_sessionId = L"";
m_username = L"";
m_characterId = 0;
m_localAddr = "";
m_discoveryAddr = "";
m_discoveryPort = 0;
m_isLoggedIn = false;
m_isHost = false;
m_gameId = 0;
m_publicRoomId = -1;
m_privateRoomId = -1;
}

View File

@@ -0,0 +1,180 @@
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "Game/ChatRoomManager.hpp"
#include "Network/Event/NotifyForcedLogout.hpp"
#include "Network/Event/NotifyFriendStatus.hpp"
#include "Database/Database.hpp"
#include "Common/Constant.hpp"
#include "logging.hpp"
UserManager::UserManager()
{
std::random_device rd;
rng.seed( rd() );
m_users.clear();
}
UserManager::~UserManager()
{
}
std::wstring UserManager::GenerateSessionId()
{
static const wchar_t charset[] = L"0123456789ABCDEF";
std::uniform_int_distribution<int> dist( 0, 15 );
std::wstring sessionId;
sessionId.reserve( MAX_SESSION_ID_LENGTH );
for( int i = 0; i < MAX_SESSION_ID_LENGTH; ++i )
{
sessionId += charset[ dist( rng ) ];
}
return sessionId;
}
sptr_user UserManager::CreateUser( sptr_socket socket, RealmGameType clientType )
{
Log::Debug( "ClientManager::CreateUser() - Created new user" );
auto user = std::make_shared< RealmUser >();
user->sock = socket;
user->m_gameType = clientType;
std::lock_guard< std::mutex > lock( m_mutex );
m_users.push_back( user );
return user;
}
void UserManager::RemoveUser( sptr_user user )
{
auto it = std::find( m_users.begin(), m_users.end(), user );
if( it == m_users.end() )
{
Log::Error( "RemoveUser : [{}] not found", user->m_sessionId );
return;
}
GameSessionManager::Get().OnDisconnectUser( user );
ChatRoomManager::Get().OnDisconnectUser( user );
NotifyFriendsOnlineStatus( user, false );
Log::Debug( "RemoveUser : [{}][{}]", user->m_username, user->m_sessionId );
std::lock_guard< std::mutex > lock( m_mutex );
m_users.erase( it );
}
void UserManager::RemoveUser( const std::wstring &sessionId )
{
if( auto user = FindUserBySessionId( sessionId ) )
{
RemoveUser( user );
}
}
void UserManager::RemoveUser( const sptr_socket socket )
{
if( auto user = FindUserBySocket( socket ) )
{
RemoveUser( user );
}
}
void UserManager::Disconnect( sptr_socket socket, const std::string reason )
{
if( nullptr == socket )
{
return;
}
Log::Debug( "DisconnectSocket : [{}]. Reason: {}", socket->remote_ip, reason );
socket->send( NotifyForcedLogout() );
socket->flag.disconnected_wait = true;
RemoveUser( socket );
}
void UserManager::Disconnect( sptr_user user, const std::string reason )
{
if( nullptr == user )
{
return;
}
if( user->sock != nullptr )
{
user->sock->send( NotifyForcedLogout() );
user->sock->flag.disconnected_wait = true;
}
Log::Debug( "DisconnectUser : [{}]. Reason: {}", user->m_sessionId, reason );
RemoveUser( user );
}
sptr_user UserManager::FindUserBySessionId( const std::wstring &sessionId )
{
std::lock_guard<std::mutex> lock( m_mutex );
auto it = std::find_if( m_users.begin(), m_users.end(), [ & ]( const sptr_user &user )
{
return user->m_sessionId == sessionId;
} );
return ( it != m_users.end() ) ? *it : nullptr;
}
sptr_user UserManager::FindUserBySocket( const sptr_socket &socket )
{
std::lock_guard<std::mutex> lock( m_mutex );
auto it = std::find_if( m_users.begin(), m_users.end(), [ & ]( const sptr_user &user )
{
return user->sock == socket;
} );
return ( it != m_users.end() ) ? *it : nullptr;
}
sptr_user UserManager::FindUserByChatHandle( const std::wstring &handle )
{
std::lock_guard<std::mutex> lock( m_mutex );
auto it = std::find_if( m_users.begin(), m_users.end(), [ & ]( const sptr_user &user )
{
return user->m_chatHandle == handle;
} );
return ( it != m_users.end() ) ? *it : nullptr;
}
int32_t UserManager::GetUserCount() const
{
return static_cast< int32_t >( m_users.size() );
}
std::vector<sptr_user> UserManager::GetUserList()
{
std::lock_guard<std::mutex> lock( m_mutex );
return m_users;
}
void UserManager::NotifyFriendsOnlineStatus( const sptr_user &user, bool onlineStatus )
{
if( !user || user->m_friendList.empty() )
{
return;
}
const auto notifyFriend = NotifyFriendStatus( user->m_chatHandle, onlineStatus );
for( const auto &friendHandle : user->m_friendList )
{
auto friendUser = FindUserByChatHandle( friendHandle );
if( friendUser && friendUser->sock )
{
friendUser->sock->send( notifyFriend );
}
}
}

View File

@@ -0,0 +1,422 @@
// ╔═╗ ╦ ╦ ╔═╗ ╔╦╗ ╔═╗ ╦ ╔═╗ ╔╗╔ ╔═╗
// ║ ╠═╣ ╠═╣ ║║║ ╠═╝ ║ ║ ║ ║║║ ╚═╗
// ╚═╝ ╩ ╩ ╩ ╩ ╩ ╩ ╩ ╩ ╚═╝ ╝╚╝ ╚═╝
// ╦ ╔═╗╔╗ ╔╗ ╦ ╦ ╔═╗╔═╗╦═╗╦ ╦╔═╗╦═╗
// ║ ║ ║╠╩╗╠╩╗╚╦╝ ╚═╗║╣ ╠╦╝╚╗╔╝║╣ ╠╦╝
// ╩═╝╚═╝╚═╝╚═╝ ╩ ╚═╝╚═╝╩╚═ ╚╝ ╚═╝╩╚═
#include "Lobby Server/LobbyServer.hpp"
#include "Game/RealmUserManager.hpp"
#include "Network/Events.hpp"
#include "configuration.hpp"
#include "logging.hpp"
LobbyServer::LobbyServer()
{
m_running = false;
m_conSocket.reset();
m_rtaSocket.reset();
m_gatewaySockets.clear();
m_clientSockets.clear();
m_recvBuffer.resize( 1024 );
}
LobbyServer::~LobbyServer()
{
Log::Info( "Lobby Server stopped." );
for( auto &socket : m_clientSockets )
{
if( socket->fd != INVALID_SOCKET )
{
closesocket( socket->fd );
}
}
for( auto &socket : m_gatewaySockets )
{
if( socket->fd != INVALID_SOCKET )
{
closesocket( socket->fd );
}
}
}
void LobbyServer::Start( std::string ip )
{
// Champions of Norrath Gateway Sockets
for( int i = 0; i < 8; i++ )
{
auto gatewaySocket = OpenListenerSocket( ip, 40800 + i, RealmGameType::CHAMPIONS_OF_NORRATH );
if( nullptr == gatewaySocket ) return;
gatewaySocket->flag.is_gateway = true;
m_gatewaySockets.push_back( gatewaySocket );
}
// Return to Arms Gateway Sockets
for( int i = 0; i < 8; i++ )
{
auto gatewaySocket = OpenListenerSocket( ip, 40810 + i, RealmGameType::RETURN_TO_ARMS );
if( nullptr == gatewaySocket ) return;
gatewaySocket->flag.is_gateway = true;
m_gatewaySockets.push_back( gatewaySocket );
}
m_conSocket = OpenListenerSocket( ip, Config::con_lobby_port, RealmGameType::CHAMPIONS_OF_NORRATH );
m_rtaSocket = OpenListenerSocket( ip, Config::rta_lobby_port, RealmGameType::RETURN_TO_ARMS );
m_running = true;
m_thread = std::thread( &LobbyServer::Run, this );
Log::Info( "Lobby Server started" );
}
void LobbyServer::Stop()
{
Log::Info( "Stopping Lobby Server..." );
for( auto &client : m_clientSockets )
{
client->send( NotifyForcedLogout() );
}
m_running = false;
if( m_thread.joinable() )
{
m_thread.join();
}
}
sptr_socket LobbyServer::OpenListenerSocket( std::string ip, int32_t port, RealmGameType type )
{
SOCKET sock = ::WSASocket( AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED );
if( sock == INVALID_SOCKET )
{
Log::Error( "WSASocket() failed on port {}", port );
return nullptr;
}
sockaddr_in service{};
service.sin_family = AF_INET;
service.sin_port = htons( port );
if( ip == "0.0.0.0" )
{
service.sin_addr.s_addr = INADDR_ANY;
}
else if( InetPtonA( AF_INET, ip.c_str(), &service.sin_addr) != 1 )
{
Log::Error( "Invalid IP address format: {}", ip );
closesocket( sock );
return nullptr;
}
if( bind( sock, reinterpret_cast< SOCKADDR * >( &service ), sizeof( service ) ) == SOCKET_ERROR )
{
Log::Error( "bind() failed on port {}", port );
closesocket( sock );
return nullptr;
}
if( listen( sock, SOMAXCONN ) == SOCKET_ERROR )
{
Log::Error( "listen() failed on port {}", port );
closesocket( sock );
return nullptr;
}
auto realmSocket = std::make_shared< RealmSocket >();
realmSocket->fd = sock;
realmSocket->remote_ip = ip;
realmSocket->remote_port = port;
realmSocket->gameType = type;
realmSocket->flag.is_listener = true;
Log::Info( "Socket Opened on {}:{}", ip, port );
return realmSocket;
}
void LobbyServer::Run()
{
FD_SET readSet;
FD_SET writeSet;
timeval timeout = { 0, 1000 };
while( m_running )
{
FD_ZERO( &readSet );
FD_ZERO( &writeSet );
FD_SET( m_conSocket->fd, &readSet );
FD_SET( m_rtaSocket->fd, &readSet );
for( const auto &sock : m_gatewaySockets )
{
if( sock != nullptr && sock->fd != INVALID_SOCKET )
{
FD_SET( sock->fd, &readSet );
}
}
CheckSocketProblem();
for( const auto &client : m_clientSockets )
{
FD_SET( client->fd, &readSet );
FD_SET( client->fd, &writeSet );
}
auto result = select( 0, &readSet, &writeSet, NULL, &timeout );
if( result == SOCKET_ERROR )
{
std::this_thread::sleep_for( std::chrono::milliseconds( 1 ) );
continue;
}
for( auto &sock : m_gatewaySockets )
{
if( sock == nullptr || sock->fd == INVALID_SOCKET )
continue;
if( FD_ISSET( sock->fd, &readSet ) )
{
AcceptConnection( sock );
}
}
if( FD_ISSET( m_conSocket->fd, &readSet ) )
{
AcceptConnection( m_conSocket );
}
if( FD_ISSET( m_rtaSocket->fd, &readSet ) )
{
AcceptConnection( m_rtaSocket );
}
for( auto &client : m_clientSockets )
{
if( FD_ISSET( client->fd, &readSet ) )
{
ReadSocket( client );
}
if( FD_ISSET( client->fd, &writeSet ) )
{
WriteSocket( client );
}
}
}
}
void LobbyServer::CheckSocketProblem()
{
auto now = std::chrono::steady_clock::now();
for( auto it = m_clientSockets.begin(); it != m_clientSockets.end(); )
{
auto &socket = *it;
auto elapsed = std::chrono::duration_cast< std::chrono::seconds >( now - socket->last_recv_time );
// Check for client timeouts
if( elapsed.count() > 30 )
{
socket->flag.disconnected_forced = true;
Log::Info( "[LOBBY] Client Timeout : ({})", socket->remote_ip );
}
// Check if we're waiting to disconnect after sending buffered data
if( socket->flag.disconnected_wait && socket->m_pendingWriteBuffer.empty() )
{
socket->flag.disconnected_forced = true;
}
if( socket->flag.disconnected_forced )
{
UserManager::Get().RemoveUser( socket );
Log::Info( "[LOBBY] Client Disconnected : ({})", socket->remote_ip );
it = m_clientSockets.erase( it );
}
else
{
++it;
}
}
}
void LobbyServer::AcceptConnection( sptr_socket srcSocket )
{
sockaddr_in clientInfo{};
int32_t addrSize = sizeof( clientInfo );
SOCKET clientSocket = accept( srcSocket->fd, ( SOCKADDR * )&clientInfo, &addrSize );
if( clientSocket == INVALID_SOCKET )
{
Log::Error( "accept() failed" );
return;
}
auto new_socket = std::make_shared< RealmSocket >();
auto gameType = srcSocket->gameType;
new_socket->fd = clientSocket;
new_socket->remote_addr = clientInfo;
new_socket->remote_ip = Util::IPFromAddr( clientInfo );
new_socket->remote_port = ntohs( clientInfo.sin_port );
new_socket->gameType = gameType;
m_clientSockets.push_back( new_socket );
if( !srcSocket->flag.is_gateway )
{
UserManager::Get().CreateUser( new_socket, gameType );
}
Log::Info( "New Client Connected : ({}) From Port {}", new_socket->remote_ip, srcSocket->remote_port );
}
void LobbyServer::ReadSocket( sptr_socket socket )
{
if( socket->flag.disconnected_forced )
{
return;
}
const auto bytesReceived = recv( socket->fd, reinterpret_cast< char * >( m_recvBuffer.data() ), static_cast< int >( m_recvBuffer.size() ), 0 );
if( bytesReceived == SOCKET_ERROR )
{
Log::Info( "Socket Error [{}].", WSAGetLastError() );
socket->flag.disconnected_forced = true;
return;
}
if( bytesReceived == 0 )
{
socket->flag.disconnected_forced = true;
return;
}
socket->last_recv_time = std::chrono::steady_clock::now();
// Append received data to socket's pending buffer
socket->m_pendingReadBuffer.insert(
socket->m_pendingReadBuffer.end(),
m_recvBuffer.begin(),
m_recvBuffer.begin() + bytesReceived
);
// Process packets in the buffer
while( socket->m_pendingReadBuffer.size() >= 4 )
{
int32_t packetSize = ntohl( *reinterpret_cast< const int32_t * >( &socket->m_pendingReadBuffer[ 0 ] ) );
if( packetSize <= 0 || packetSize > 2048 )
{
Log::Error( "Invalid packet size: {}. Disconnecting client.", packetSize );
socket->flag.disconnected_wait = true;
break;
}
if( socket->m_pendingReadBuffer.size() < static_cast< size_t >( packetSize ) )
{
break;
}
auto stream = std::make_shared< ByteBuffer >( socket->m_pendingReadBuffer.data() + 4, packetSize - 4 );
// Erase the packet from the buffer
socket->m_pendingReadBuffer.erase(
socket->m_pendingReadBuffer.begin(),
socket->m_pendingReadBuffer.begin() + packetSize
);
if( stream->m_buffer.size() == 0 ) break;
// Process the packet
HandleRequest( socket, stream );
}
}
void LobbyServer::WriteSocket( sptr_socket socket )
{
if( socket->flag.disconnected_forced || socket->m_pendingWriteBuffer.empty() )
return;
socket->last_send_time = std::chrono::steady_clock::now();
size_t totalBytesSent = 0;
const size_t bufferSize = socket->m_pendingWriteBuffer.size();
while( totalBytesSent < bufferSize )
{
const size_t remaining = bufferSize - totalBytesSent;
const int chunkSize = static_cast< int >( std::min<size_t>( remaining, 1024 ) );
int bytesSent = send(
socket->fd,
reinterpret_cast< const char * >( socket->m_pendingWriteBuffer.data() + totalBytesSent ),
chunkSize,
0
);
if( bytesSent == SOCKET_ERROR )
{
const int err = WSAGetLastError();
if( err == WSAEWOULDBLOCK )
break;
Log::Error( "Send failed: {}", err );
socket->flag.disconnected_wait = true;
return;
}
if( bytesSent == 0 )
{
socket->flag.disconnected_wait = true;
return;
}
totalBytesSent += bytesSent;
if( bytesSent < chunkSize )
{
break;
}
}
std::lock_guard< std::mutex > lock( socket->write_mutex );
socket->m_pendingWriteBuffer.erase(
socket->m_pendingWriteBuffer.begin(),
socket->m_pendingWriteBuffer.begin() + totalBytesSent
);
}
void LobbyServer::HandleRequest( sptr_socket socket, sptr_byte_stream stream )
{
auto packetId = stream->read< uint16_t >();
stream->set_position( 0 );
auto it = REQUEST_EVENT.find( packetId );
if( it == REQUEST_EVENT.end() )
{
Log::Error( "[LOBBY] Unknown packet id : {}", packetId );
Log::Packet( stream->m_buffer, stream->m_buffer.size(), false );
return;
}
auto request = it->second();
if( auto res = request->ProcessRequest( socket, stream ) )
{
socket->send( res );
}
}

View File

@@ -0,0 +1,17 @@
#include "Network\Event\NotifyClientDiscovered.hpp"
#include "Game/RealmUser.hpp"
NotifyClientDiscovered::NotifyClientDiscovered( sptr_user user ) : GenericMessage( 0x40 )
{
this->m_clientIp = user->m_discoveryAddr;
this->m_clientPort = user->m_discoveryPort;
}
void NotifyClientDiscovered::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
out.write_sz_utf8( m_clientIp );
out.write_u32( m_clientPort );
}

View File

@@ -0,0 +1,19 @@
#include "Network\Event\NotifyClientDiscovered_RTA.hpp"
#include "Common/Constant.hpp"
#include "Game/RealmUser.hpp"
NotifyClientDiscovered_RTA::NotifyClientDiscovered_RTA( sptr_user user ) : GenericMessage( 0x40 )
{
this->m_clientIp = user->m_discoveryAddr;
this->m_clientPort = user->m_discoveryPort;
}
void NotifyClientDiscovered_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
out.write_utf8( m_clientIp );
out.write_u32( m_clientPort );
}

View File

@@ -0,0 +1,18 @@
#include "NotifyClientReqConnect.h"
NotifyClientRequestConnect::NotifyClientRequestConnect( std::string clientIp, int32_t clientPort ) : GenericMessage( 0x3F )
{
this->m_clientIp = std::move( clientIp );
this->m_clientPort = clientPort;
}
ByteBuffer &NotifyClientRequestConnect::Serialize()
{
m_stream.write_u16( m_packetId );
m_stream.write_u32( 0 );
m_stream.write_sz_utf8( m_clientIp );
m_stream.write_u32( m_clientPort );
return m_stream;
}

View File

@@ -0,0 +1,17 @@
#include "Network/Event/NotifyClientRequestConnect.hpp"
#include "Game/RealmUser.hpp"
NotifyClientRequestConnect::NotifyClientRequestConnect( sptr_user user ) : GenericMessage( 0x3F )
{
this->m_clientIp = user->m_discoveryAddr;
this->m_clientPort = user->m_discoveryPort;
}
void NotifyClientRequestConnect::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
out.write_sz_utf8( m_clientIp );
out.write_u32( m_clientPort );
}

View File

@@ -0,0 +1,25 @@
#include "Network\Event\NotifyClientRequestConnect_RTA.hpp"
#include "Game/RealmUser.hpp"
NotifyClientRequestConnect_RTA::NotifyClientRequestConnect_RTA( sptr_user user ) : GenericMessage( 0x65 )
{
this->m_remoteAddr = user->m_discoveryAddr;
this->m_remotePort = user->m_discoveryPort;
this->m_localAddr = user->m_localAddr;
this->m_localPort = user->m_localPort;
}
void NotifyClientRequestConnect_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
out.write_utf8( this->m_localAddr );
out.write_u32( this->m_localPort );
out.write_utf8( this->m_remoteAddr );
out.write_u32( this->m_remotePort );
}

View File

@@ -0,0 +1,12 @@
#include "Network/Event/NotifyForcedLogout.hpp"
NotifyForcedLogout::NotifyForcedLogout() : GenericMessage( 0x41 )
{
}
void NotifyForcedLogout::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
out.write_u32( 0 );
}

View File

@@ -0,0 +1,16 @@
#include "Network\Event\NotifyFriendStatus.hpp"
NotifyFriendStatus::NotifyFriendStatus( std::wstring handle, bool status ) : GenericMessage( 0x2F )
{
m_handle = handle;
m_status = status;
}
void NotifyFriendStatus::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
out.write_utf16( m_handle );
out.write_u8( m_status ? 1 : 0 );
}

View File

@@ -0,0 +1,22 @@
#include "Network/Event/NotifyGameDiscovered.hpp"
#include "Game/RealmUser.hpp"
NotifyGameDiscovered::NotifyGameDiscovered( sptr_user user ) : GenericMessage( 0x3E )
{
this->m_clientIp = user->m_discoveryAddr;
this->m_clientPort = user->m_discoveryPort;
this->m_clientType = user->m_gameType;
}
void NotifyGameDiscovered::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
if( m_clientType == RealmGameType::RETURN_TO_ARMS )
out.write_utf8( m_clientIp );
else
out.write_sz_utf8( m_clientIp );
out.write_u32( m_clientPort );
}

View File

@@ -0,0 +1,16 @@
#include "Network\Event\NotifyInstantMessage.hpp"
NotifyInstantMessage::NotifyInstantMessage( std::wstring chatHandle, std::wstring message ) : GenericMessage( 0x30 )
{
m_chatHandle = chatHandle;
m_message = message;
}
void NotifyInstantMessage::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
out.write_utf16( m_chatHandle );
out.write_utf16( m_message );
}

View File

@@ -0,0 +1,19 @@
#include "NotifyReserveUserSlot_RTA.h"
NotifyReserveUserSlot_RTA::NotifyReserveUserSlot_RTA( int32_t memberId, std::string discoveryAddr, int32_t discoveryPort ) : GenericMessage( 0x51 )
{
this->m_discoveryAddr = discoveryAddr;
this->m_port = discoveryPort;
this->m_memberId = memberId;
}
// This notification is nullsub.
void NotifyReserveUserSlot_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
out.write_u32( 0 );
out.write_utf8( "" );
out.write_u32( 0 );
}

View File

@@ -0,0 +1,18 @@
#include "Network\Event\NotifyRoomMessage.hpp"
NotifyRoomMessage::NotifyRoomMessage( std::wstring roomName, std::wstring chatHandle, std::wstring message ) : GenericMessage( 0x3D )
{
m_roomName = roomName;
m_chatHandle = chatHandle;
m_message = message;
}
void NotifyRoomMessage::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
out.write_utf16( m_roomName );
out.write_utf16( m_chatHandle );
out.write_utf16( m_message );
}

View File

@@ -0,0 +1,49 @@
#include "Notify_4C.h"
Notify_4C::Notify_4C() : GenericMessage( 0x4C )
{
}
void Notify_4C::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( 0 );
out.write_u32( 0 ); // Unknown
out.write_u32( 1 ); // Unknown
{
out.write_utf16( L"Dummy 1" );
}
out.write_u32( 1 ); // Unknown
{
out.write_u32( 0 );
// Blob
}
out.write_u32( 1 ); // Unknown
{
out.write_u32( 0 );
}
out.write_u16( 0 ); // Unknown
out.write_u32( 1 ); // Unknown
{
out.write_u16( 0 );
out.write_u32( 0 );
out.write_utf16( L"Dummy 2" );
out.write_u32( 0 );
out.write_u32( 0 );
out.write_u32( 0 );
out.write_u16( 1 );
}
out.write_u32( 0 ); // Unknown
out.write_utf8( "127.0.0.1" );
out.write_u32( 0 ); // Unknown
out.write_u32( 0 ); // Unknown
}

View File

@@ -0,0 +1,22 @@
#include "Notify_65.h"
NotifyClientRequestsConnect2::NotifyClientRequestsConnect2( std::string discoveryAddr, std::string localAddr, int32_t port ) : GenericMessage( 0x65 )
{
m_discoveryAddr = std::move( discoveryAddr );
m_localAddr = std::move( localAddr );
m_port = port;
}
ByteBuffer &NotifyClientRequestsConnect2::Serialize()
{
m_stream.write_u16( m_packetId );
m_stream.write_u32( 0 );
m_stream.write_utf8( m_discoveryAddr );
m_stream.write_u32( m_port );
m_stream.write_utf8( m_localAddr );
m_stream.write_u32( m_port );
return m_stream;
}

View File

@@ -0,0 +1,61 @@
#include "Network/Event/RequestAddFriend.hpp"
#include "Game/RealmUserManager.hpp"
#include "Database/Database.hpp"
#include "logging.hpp"
void RequestAddFriend::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_chatHandle = stream->read_utf16();
}
sptr_generic_response RequestAddFriend::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultAddFriend >( this, FATAL_ERROR );
}
if( user->IsFriend( m_chatHandle ) )
{
return std::make_shared< ResultAddFriend >( this, FRIEND_DUPLICATE );
}
auto targetUser = UserManager::Get().FindUserByChatHandle( m_chatHandle );
if( targetUser == nullptr )
{
return std::make_shared< ResultAddFriend >( this, FRIEND_INVALID );
}
if( targetUser->IsIgnored( user->m_chatHandle ) )
{
return std::make_shared< ResultAddFriend >( this, FRIEND_IGNORING );
}
if( !Database::Get().SaveFriend( user->m_accountId, targetUser->m_chatHandle ) )
{
return std::make_shared< ResultAddFriend >( this, DATABASE_ERROR );
}
user->m_friendList.push_back( targetUser->m_chatHandle );
return std::make_shared< ResultAddFriend >( this, SUCCESS );
}
ResultAddFriend::ResultAddFriend( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultAddFriend::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,61 @@
#include "Network/Event/RequestAddIgnore.hpp"
#include "Game/RealmUserManager.hpp"
#include "Database/Database.hpp"
#include "logging.hpp"
void RequestAddIgnore::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_chatHandle = stream->read_utf16();
}
sptr_generic_response RequestAddIgnore::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultAddIgnore >( this, FATAL_ERROR );
}
if( user->IsFriend( m_chatHandle ) )
{
return std::make_shared< ResultAddIgnore >( this, IGNORE_FRIEND );
}
if( user->IsIgnored( m_chatHandle ) )
{
return std::make_shared< ResultAddIgnore >( this, IGNORE_DUPLICATE );
}
auto targetUser = UserManager::Get().FindUserByChatHandle( m_chatHandle );
if( targetUser == nullptr )
{
return std::make_shared< ResultAddIgnore >( this, IGNORE_INVALID );
}
if( !Database::Get().SaveIgnore( user->m_accountId, targetUser->m_chatHandle ) )
{
return std::make_shared< ResultAddIgnore >( this, DATABASE_ERROR );
}
user->m_ignoreList.push_back( targetUser->m_chatHandle );
return std::make_shared< ResultAddIgnore >( this, SUCCESS );
}
ResultAddIgnore::ResultAddIgnore( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultAddIgnore::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,45 @@
#include "Network/Event/RequestAppendCharacterData.hpp"
#include "Game/CharacterSaveManager.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/RealmUser.hpp"
#include "Database/Database.hpp"
#include "logging.hpp"
void RequestAppendCharacterData::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
auto sessionId = stream->read_encrypted_utf16();
auto length = stream->read_u32();
m_data = stream->read_bytes( length );
m_endOfData = stream->read_u32();
}
sptr_generic_response RequestAppendCharacterData::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultAppendCharacterData >( this, FATAL_ERROR );
}
CharacterSaveManager::Get().AppendSaveData( user->m_sessionId, m_data, m_endOfData );
return std::make_shared< ResultAppendCharacterData >( this, SUCCESS );
}
ResultAppendCharacterData::ResultAppendCharacterData( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultAppendCharacterData::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,47 @@
#include "Network/Event/RequestCancelGame.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "logging.hpp"
void RequestCancelGame::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
}
sptr_generic_response RequestCancelGame::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", m_sessionId );
return std::make_shared< ResultCancelGame >( this );
}
if( user->m_gameType != RealmGameType::CHAMPIONS_OF_NORRATH )
{
return std::make_shared< ResultCancelGame >( this );
}
if( !GameSessionManager::Get().RequestCancel( user ) )
{
Log::Error( "Failed to cancel game session for user [{}]", user->m_sessionId );
}
return std::make_shared< ResultCancelGame >( this );
}
ResultCancelGame::ResultCancelGame( GenericRequest *request ) : GenericResponse( *request )
{
}
void ResultCancelGame::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
}

View File

@@ -0,0 +1,61 @@
#include "Network/Event/RequestCancelGame_RTA.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "Game/ChatRoomManager.hpp"
#include "logging.hpp"
void RequestCancelGame_RTA::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
}
sptr_generic_response RequestCancelGame_RTA::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", m_sessionId );
return std::make_shared< ResultCancelGame_RTA >( this );
}
if( user->m_gameType != RealmGameType::RETURN_TO_ARMS )
{
return std::make_shared< ResultCancelGame_RTA >( this );
}
const auto &gameSession = GameSessionManager::Get().FindGame( user->m_gameId, user->m_gameType );
if( gameSession == nullptr )
{
Log::Error( "Game session not found for user [{}]", user->m_sessionId );
return std::make_shared< ResultCancelGame_RTA >( this );
}
if( !GameSessionManager::Get().RequestCancel( user ) )
{
Log::Error( "Failed to cancel game session for user [{}]", user->m_sessionId );
}
if( !ChatRoomManager::Get().LeaveRoom( user, user->m_privateRoomId ) )
{
Log::Error( "Failed to leave private chat room for user [{}]", user->m_username );
}
return std::make_shared< ResultCancelGame_RTA >( this );
}
ResultCancelGame_RTA::ResultCancelGame_RTA( GenericRequest *request ) : GenericResponse( *request )
{
}
void ResultCancelGame_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
}

View File

@@ -0,0 +1,97 @@
#include "Network/Event/RequestCreateAccount.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/RealmUser.hpp"
#include "Common/Constant.hpp"
#include "Crypto/PasswordHash.hpp"
#include "Database/Database.hpp"
#include "logging.hpp"
void RequestCreateAccount::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_username = stream->read_encrypted_utf16();
m_password = stream->read_encrypted_utf16();
m_emailAddress = stream->read_encrypted_utf16();
m_dateOfBirth = stream->read_encrypted_utf16();
m_chatHandle = stream->read_encrypted_utf16();
}
bool RequestCreateAccount::VerifyUserData()
{
if( m_username.empty() || m_password.empty() || m_emailAddress.empty() || m_dateOfBirth.empty() || m_chatHandle.empty() )
{
Log::Error( "RequestCreateAccount::VerifyUserData() - Missing required fields for account creation." );
return false;
}
if( m_username.length() < 3 || m_username.length() > 20 )
{
Log::Error( "RequestCreateAccount::VerifyUserData() - Username must be between 3 and 20 characters." );
return false;
}
if( m_password.length() < 6 || m_password.length() > 32 )
{
Log::Error( "RequestCreateAccount::VerifyUserData() - Password must be between 6 and 32 characters." );
return false;
}
return true;
}
sptr_generic_response RequestCreateAccount::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( nullptr == user || user->m_gameType != RealmGameType::RETURN_TO_ARMS )
{
return std::make_shared< ResultCreateAccount >( this, ERROR_FATAL, L"" );
}
if( m_username.empty() || m_password.empty() || m_emailAddress.empty() || m_dateOfBirth.empty() || m_chatHandle.empty() )
{
Log::Error( "RequestCreateAccount::ProcessRequest() - Missing required fields for account creation." );
return std::make_shared< ResultCreateAccount >( this, ERROR_FATAL, L"" );
}
auto result = Database::Get().CreateNewAccount
(
Util::WideToUTF8( m_username ),
Util::WideToUTF8( m_password ),
Util::WideToUTF8( m_emailAddress ),
Util::WideToUTF8( m_dateOfBirth ),
Util::WideToUTF8( m_chatHandle )
);
if( !result )
{
Log::Error( "RequestCreateAccount::ProcessRequest() - Failed to create account for user: {}", m_username );
return std::make_shared< ResultCreateAccount >( this, ERROR_FATAL, L"" );
}
user->m_isLoggedIn = true;
user->m_sessionId = UserManager::Get().GenerateSessionId();
user->m_accountId = result;
user->m_username = m_username;
user->m_chatHandle = m_chatHandle;
return std::make_shared< ResultCreateAccount >( this, SUCCESS, user->m_sessionId );
}
ResultCreateAccount::ResultCreateAccount( GenericRequest *request, int32_t reply, std::wstring sessionId ) : GenericResponse( *request )
{
m_reply = reply;
m_sessionId = sessionId;
}
void ResultCreateAccount::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
out.write_encrypted_utf16( m_sessionId );
}

View File

@@ -0,0 +1,64 @@
#include "Network/Event/RequestCreateNewCharacter_RTA.hpp"
#include "Database/Database.hpp"
#include "Game/CharacterSaveManager.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/RealmUser.hpp"
#include "Game/RealmCharacter.hpp"
#include "logging.hpp"
void RequestCreateNewCharacter_RTA::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
auto a = stream->read_u32();
auto b = stream->read_u32();
m_metaData.Deserialize( stream );
auto characterDataSize = stream->read_u32();
m_characterData = stream->read_bytes( characterDataSize );
}
sptr_generic_response RequestCreateNewCharacter_RTA::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", m_sessionId );
return std::make_shared< ResultCreateNewCharacter_RTA >( this, FATAL_ERROR );
}
if( user->m_gameType != RealmGameType::RETURN_TO_ARMS )
{
Log::Error( "Invalid game type for CreateNewCharacter_RTA request! [{}]", m_sessionId );
return std::make_shared< ResultCreateNewCharacter_RTA >( this, FATAL_ERROR );
}
auto &saveManager = CharacterSaveManager::Get();
if( !saveManager.BeginSaveTask( user, user, 0, m_metaData, CharacterSaveType::NEW_CHARACTER ) )
{
Log::Error( "Failed to begin save task for new character! [{}]", m_sessionId );
return std::make_shared< ResultCreateNewCharacter_RTA >( this, FATAL_ERROR );
}
saveManager.AppendSaveData( user->m_sessionId, m_characterData, false );
return std::make_shared< ResultCreateNewCharacter_RTA >( this, SUCCESS );
}
ResultCreateNewCharacter_RTA::ResultCreateNewCharacter_RTA( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultCreateNewCharacter_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,61 @@
#include "Network/Event/RequestCreatePrivateGame.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "configuration.hpp"
#include "logging.hpp"
void RequestCreatePrivateGame::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_gameName = stream->read_utf16();
}
sptr_generic_response RequestCreatePrivateGame::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", m_sessionId );
return std::make_shared< ResultCreatePrivateGame >( this, FATAL_ERROR, "", 0 );
}
if( nullptr != GameSessionManager::Get().FindGame( m_gameName, user->m_gameType ) )
{
Log::Error( "Game name is already in use! [{}]", m_gameName );
return std::make_shared< ResultCreatePrivateGame >( this, GAME_NAME_IN_USE, "", 0 );
}
auto result = GameSessionManager::Get().CreateGameSession_CON( user, L"", m_gameName, L"", true);
if( !result )
{
Log::Error( "RequestCreatePrivateGame::ProcessRequest() - Failed to create private game session!" );
return std::make_shared< ResultCreatePrivateGame >( this, GENERAL_ERROR, "", 0 );
}
Log::Info( "[{}] Create Private Game: {}", m_sessionId, m_gameName );
return std::make_shared< ResultCreatePrivateGame >( this, SUCCESS, Config::service_ip, Config::discovery_port );
}
ResultCreatePrivateGame::ResultCreatePrivateGame( GenericRequest *request, int32_t reply, std::string discoveryIp, int32_t discoveryPort ) : GenericResponse( *request )
{
m_reply = reply;
m_discoveryIp = discoveryIp;
m_discoveryPort = discoveryPort;
}
void ResultCreatePrivateGame::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
out.write_sz_utf8( m_discoveryIp );
out.write( m_discoveryPort );
}

View File

@@ -0,0 +1,59 @@
#include "Network/Event/RequestCreatePrivateGame_RTA.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "configuration.hpp"
#include "logging.hpp"
void RequestCreatePrivateGame_RTA::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_gameName = stream->read_utf16();
m_localAddr = Util::WideToUTF8( stream->read_utf16() );
m_localPort = stream->read_u32();
}
sptr_generic_response RequestCreatePrivateGame_RTA::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", m_sessionId );
return std::make_shared< ResultCreatePrivateGame_RTA >( this, FATAL_ERROR, "", 0 );
}
auto result = GameSessionManager::Get().CreateGameSession_RTA( user, L"", m_gameName, {}, true);
if( !result )
{
Log::Error( "RequestCreatePrivateGame2::ProcessRequest() - Failed to create private game session!" );
return std::make_shared< ResultCreatePrivateGame_RTA >( this, GENERAL_ERROR, "", 0 );
}
user->m_localAddr = m_localAddr;
user->m_localPort = m_localPort;
Log::Info( "[{}] Create Private Game: {}", m_sessionId, m_gameName );
return std::make_shared< ResultCreatePrivateGame_RTA >( this, SUCCESS, Config::service_ip, Config::discovery_port );
}
ResultCreatePrivateGame_RTA::ResultCreatePrivateGame_RTA( GenericRequest *request, int32_t reply, std::string discoveryIp, int32_t discoveryPort ) : GenericResponse( *request )
{
m_reply = reply;
m_discoveryIp = discoveryIp;
m_discoveryPort = discoveryPort;
}
void ResultCreatePrivateGame_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
out.write_utf8( m_discoveryIp );
out.write( m_discoveryPort );
}

View File

@@ -0,0 +1,45 @@
#include "Network/Event/RequestCreatePrivateRoom.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/ChatRoomManager.hpp"
#include "logging.hpp"
void RequestCreatePrivateRoom::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_roomName = stream->read_utf16();
}
sptr_generic_response RequestCreatePrivateRoom::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultCreatePrivateRoom >( this );
}
auto result = ChatRoomManager::Get().CreateGameChatSession( user, m_roomName );
if( !result )
{
Log::Error( "RequestCreatePrivateRoom::ProcessRequest() - Failed to create private room!" );
return std::make_shared< ResultCreatePrivateRoom >( this );
}
return std::make_shared< ResultCreatePrivateRoom >( this );
}
ResultCreatePrivateRoom::ResultCreatePrivateRoom( GenericRequest *request ) : GenericResponse( *request )
{
}
void ResultCreatePrivateRoom::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
}

View File

@@ -0,0 +1,96 @@
#include "Network/Event/RequestCreatePublicGame.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "configuration.hpp"
#include "logging.hpp"
// Request
void RequestCreatePublicGame::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
// Some kind of match attributes
auto unknown_a = stream->read_u16();
auto unknown_b = stream->read_u32();
auto unknown_c = stream->read_u32();
auto unknown_d = stream->read_u32();
m_gameInfo = stream->read_utf16();
auto [name, stage] = ParseNameAndStage( m_gameInfo );
m_gameName = name;
m_stageName = stage;
}
std::tuple<std::wstring, std::wstring> RequestCreatePublicGame::ParseNameAndStage( const std::wstring &gameInfo )
{
const size_t open = gameInfo.find( L'[' );
const size_t close = gameInfo.find( L']' );
if( open == std::wstring::npos || close == std::wstring::npos || close < open )
return { L"", L"" };
std::wstring name = gameInfo.substr( 0, open );
std::wstring stage = gameInfo.substr( open + 1, close - open - 1 );
if( !name.empty() && iswspace( name.back() ) )
name.pop_back();
return { name, stage };
}
sptr_generic_response RequestCreatePublicGame::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", m_sessionId );
return std::make_shared< ResultCreatePublicGame >( this, FATAL_ERROR, "", 0 );
}
if( nullptr != GameSessionManager::Get().FindGame( m_gameName, user->m_gameType ) )
{
Log::Error( "Game name is already in use! [{}]", m_gameName );
return std::make_shared< ResultCreatePublicGame >( this, GAME_NAME_IN_USE, "", 0 );
}
auto result = GameSessionManager::Get().CreateGameSession_CON( user, m_gameInfo, m_gameName, m_stageName, false );
if( !result )
{
Log::Error( "RequestCreatePublicGame::ProcessRequest() - Failed to create public game session!" );
return std::make_shared< ResultCreatePublicGame >( this, GENERAL_ERROR, "", 0 );
}
Log::Info( "[{}] Create Public Game: {}", m_sessionId, m_gameInfo );
user->m_isHost = true;
user->m_discoveryAddr = "";
user->m_discoveryPort = 0;
return std::make_shared< ResultCreatePublicGame >( this, SUCCESS, Config::service_ip, Config::discovery_port );
}
// Result
ResultCreatePublicGame::ResultCreatePublicGame( GenericRequest *request, int32_t reply, std::string discoveryIp, int32_t discoveryPort ) : GenericResponse( *request )
{
m_reply = reply;
m_discoveryIp = discoveryIp;
m_discoveryPort = discoveryPort;
}
void ResultCreatePublicGame::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
out.write_sz_utf8( m_discoveryIp );
out.write( m_discoveryPort );
}

View File

@@ -0,0 +1,114 @@
#include "Network/Event/RequestCreatePublicGame_RTA.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "Game/ChatRoomManager.hpp"
#include "configuration.hpp"
#include "logging.hpp"
void RequestCreatePublicGame_RTA::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
auto unknown_a = stream->read_u16();
auto unknown_b = stream->read_u32();
auto unknown_c = stream->read_u32();
auto unknown_d = stream->read_u32();
m_gameInfo = stream->read_utf16();
auto unknown_e = stream->read_u32();
auto unknown_f = stream->read_u32();
m_localAddr = Util::WideToUTF8( stream->read_utf16() );
m_localPort = stream->read_u32();
if( !ParseGameInfo() )
{
Log::Error( "Failed to parse game info: {}", m_gameInfo );
}
}
bool RequestCreatePublicGame_RTA::ParseGameInfo()
{
if( m_gameInfo.empty() )
{
Log::Error( "Game info is empty!" );
return false;
}
size_t pipePos = m_gameInfo.find( L'|' );
if( pipePos == std::wstring::npos )
{
Log::Error( "Invalid game info format!" );
return false;
}
std::wstring numbersPart = m_gameInfo.substr( 0, pipePos );
m_gameName = m_gameInfo.substr( pipePos + 1 );
std::wstringstream ss( numbersPart );
std::wstring numStr;
std::array<int8_t, 5> fields = { 0, 0, 0, 0, 0 };
for( size_t i = 0; i < fields.size(); ++i )
{
if( !std::getline( ss, numStr, L',' ) )
return false;
try
{
fields[ i ] = std::stoi( numStr );
}
catch( ... )
{
return false;
}
}
return true;
}
sptr_generic_response RequestCreatePublicGame_RTA::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", m_sessionId );
return std::make_shared< ResultCreatePublicGame_RTA >( this, GENERAL_ERROR, "", 0 );
}
auto result = GameSessionManager::Get().CreateGameSession_RTA( user, m_gameInfo, m_gameName, m_attributes, false );
if( !result )
{
Log::Error( "RequestCreatePublicGame::ProcessRequest() - Failed to create public game session!" );
return std::make_shared< ResultCreatePublicGame_RTA >( this, GENERAL_ERROR, "", 0 );
}
user->m_localAddr = m_localAddr;
user->m_localPort = m_localPort;
Log::Info( "[{}] Create Public Game: {}", user->m_username, m_gameInfo );
return std::make_shared< ResultCreatePublicGame_RTA >( this, SUCCESS, Config::service_ip, Config::discovery_port );
}
// Result
ResultCreatePublicGame_RTA::ResultCreatePublicGame_RTA( GenericRequest *request, int32_t reply, std::string discoveryIp, int32_t discoveryPort ) : GenericResponse( *request )
{
m_reply = reply;
m_discoveryIp = discoveryIp;
m_discoveryPort = discoveryPort;
}
void ResultCreatePublicGame_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
out.write_utf8( m_discoveryIp );
out.write( m_discoveryPort );
}

View File

@@ -0,0 +1,58 @@
#include "Network/Event/RequestDoClientDiscovery.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "configuration.hpp"
#include "logging.hpp"
void RequestDoClientDiscovery::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_gameId = stream->read_u32();
}
sptr_generic_response RequestDoClientDiscovery::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultDoClientDiscovery >( this, DISCOVERY_REPLY::FATAL_ERROR, "", 0 );
}
auto session = GameSessionManager::Get().FindGame( m_gameId, user->m_gameType );
if( session == nullptr )
{
return std::make_shared< ResultDoClientDiscovery >( this, DISCOVERY_REPLY::FATAL_ERROR, "", 0 );
}
if( !session->IsJoinable( user ) )
{
return std::make_shared< ResultDoClientDiscovery >( this, DISCOVERY_REPLY::GAME_FULL, "", 0 );
}
user->m_gameId = m_gameId;
user->m_isHost = false;
return std::make_shared< ResultDoClientDiscovery >( this, DISCOVERY_REPLY::SUCCESS, Config::service_ip, Config::discovery_port );
}
ResultDoClientDiscovery::ResultDoClientDiscovery( GenericRequest *request, int32_t reply, std::string ip, int32_t port ) : GenericResponse( *request )
{
m_reply = reply;
m_discoveryIP = ip;
m_discoveryPort = port;
}
void ResultDoClientDiscovery::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
out.write_sz_utf8( m_discoveryIP );
out.write( m_discoveryPort );
}

View File

@@ -0,0 +1,59 @@
#include "Network/Event/RequestDoClientDiscovery_RTA.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "configuration.hpp"
#include "logging.hpp"
void RequestDoClientDiscovery_RTA::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_gameId = stream->read_u32();
m_localAddr = stream->read_utf16();
m_localPort = stream->read_u32();
}
sptr_generic_response RequestDoClientDiscovery_RTA::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", m_sessionId );
return std::make_shared< ResultDoClientDiscovery_RTA >( this, TIMEOUT );
}
auto session = GameSessionManager::Get().FindGame( m_gameId, user->m_gameType );
if( session == nullptr )
{
Log::Error( "Game session not found! [{}]", m_gameId );
return std::make_shared< ResultDoClientDiscovery_RTA >( this, NOT_FOUND );
}
user->m_isHost = false;
user->m_gameId = session->m_gameId;
user->m_localAddr = Util::WideToUTF8(m_localAddr);
user->m_localPort = m_localPort;
return std::make_shared< ResultDoClientDiscovery_RTA >( this, SUCCESS, Config::service_ip, Config::discovery_port );
}
ResultDoClientDiscovery_RTA::ResultDoClientDiscovery_RTA( GenericRequest *request, int32_t reply, std::string discoveryIp, int32_t discoveryPort ) : GenericResponse( *request )
{
m_reply = reply;
m_discoveryIp = discoveryIp;
m_discoveryPort = discoveryPort;
}
void ResultDoClientDiscovery_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
out.write_utf8( m_discoveryIp );
out.write_u32( m_discoveryPort );
}

View File

@@ -0,0 +1,85 @@
#include "Network/Event/RequestEnterRoom.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/ChatRoomManager.hpp"
#include "logging.hpp"
void RequestEnterRoom::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
auto sessionId = stream->read_encrypted_utf16();
m_roomName = stream->read_utf16();
}
sptr_generic_response RequestEnterRoom::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
const auto user = UserManager::Get().FindUserBySocket( socket );
if( !user )
{
Log::Error( "User not found for socket!" );
return std::make_shared< ResultEnterRoom >( this, GENERAL_ERROR, nullptr );
}
if( !ChatRoomManager::Get().JoinRoom( user, m_roomName ) )
{
Log::Error( "Failed to join room [{}] for user [{}]", m_roomName, user->m_username );
return std::make_shared< ResultEnterRoom >( this, GENERAL_ERROR, nullptr );
}
const auto room = ChatRoomManager::Get().FindRoom( m_roomName );
if( !room )
{
Log::Error( "Chat room [{}] not found after joining", m_roomName );
return std::make_shared< ResultEnterRoom >( this, GENERAL_ERROR, nullptr );
}
return std::make_shared< ResultEnterRoom >( this, SUCCESS, room );
}
ResultEnterRoom::ResultEnterRoom( GenericRequest *request, int32_t reply, sptr_chat_room_session room ) : GenericResponse( *request )
{
m_reply = reply;
m_room = room;
}
void ResultEnterRoom::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
if( m_room )
{
out.write_utf16( m_room->m_name );
out.write_utf16( m_room->m_banner );
out.write_u32( static_cast< uint32_t >( m_room->m_members.size() ) );
for( const auto &m : m_room->m_members )
{
if( auto member = m.lock() )
{
out.write_utf16( member->m_chatHandle );
}
else
{
out.write_utf16( L"Unknown" );
}
}
out.write_u32( static_cast< uint32_t >( m_room->m_moderators.size() ) );
for( const auto &m : m_room->m_moderators )
{
if( auto member = m.lock() )
{
out.write_utf16( member->m_chatHandle );
}
else
{
out.write_utf16( L"" );
}
}
}
}

View File

@@ -0,0 +1,113 @@
#include "Network/Event/RequestGetCharacterData_RTA.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/RealmUser.hpp"
#include "Database/Database.hpp"
#include "logging.hpp"
void RequestGetNetCharacterData_RTA::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
auto sessionId = stream->read_encrypted_utf16();
m_characterId = stream->read_u32();
}
sptr_generic_response RequestGetNetCharacterData_RTA::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultGetNetCharacterData_RTA >( this, FATAL_ERROR );
}
auto result = Database::Get().LoadCharacterData( user->m_accountId, m_characterId );
if( !result )
{
Log::Error( "Failed to load character data for account ID: " + std::to_string( user->m_accountId ) + ", character ID: " + std::to_string( m_characterId ) );
return std::make_shared< ResultGetNetCharacterData_RTA >( this, FATAL_ERROR );
}
user->m_characterId = result->m_characterId;
user->m_character = result;
return SendCharacterData( socket, result );
}
sptr_generic_response RequestGetNetCharacterData_RTA::SendCharacterData( sptr_socket socket, sptr_realm_character character )
{
const auto &data = character->m_data;
const auto data_size = data.size();
size_t position = 0;
sptr_generic_response finalChunk = nullptr;
while( position < data_size )
{
const size_t chunk_size = std::min<size_t>( 1024, data_size - position );
std::vector<uint8_t> chunk_data( data.begin() + position, data.begin() + position + chunk_size );
const int32_t isFinalChunk = ( position + chunk_size >= data_size );
if( isFinalChunk && chunk_data.size() < 1024 )
{
chunk_data.resize( 1024, 0 );
}
auto response = std::make_shared<ResultGetNetCharacterData_RTA>( this, SUCCESS, std::move( chunk_data ), isFinalChunk );
if( isFinalChunk )
{
finalChunk = response;
}
else
{
socket->send( response );
}
position += chunk_size;
}
return finalChunk;
}
ResultGetNetCharacterData_RTA::ResultGetNetCharacterData_RTA( GenericRequest *request, int32_t reply, std::optional<std::vector<uint8_t>> data, std::optional<int32_t> endOfData ) : GenericResponse( *request )
{
m_reply = reply;
if( !data.has_value() )
{
m_chunk.clear();
}
else
{
m_chunk = data.value();
}
if( !endOfData.has_value() )
{
m_endOfData = 0;
}
else
{
m_endOfData = endOfData.value();
}
}
void ResultGetNetCharacterData_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
if( !m_reply )
{
out.write_u32( static_cast< uint32_t >( m_chunk.size() ) );
out.write_bytes( m_chunk );
out.write_u32( m_endOfData );
}
}

View File

@@ -0,0 +1,34 @@
#include "Network/Event/RequestGetEncryptionKey.hpp"
void RequestGetEncryptionKey::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
}
sptr_generic_response RequestGetEncryptionKey::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto publicKey = stream->read_utf8();
auto unknown = stream->read_u32();
return std::make_shared< ResultGetEncryptionKey >( this );
}
ResultGetEncryptionKey::ResultGetEncryptionKey( GenericRequest *request ) : GenericResponse( *request )
{
m_symKey = RealmCrypt::getSymmetricKey();
}
void ResultGetEncryptionKey::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
const auto encrypted = RealmCrypt::encryptSymmetric( m_symKey );
out.write_u32( static_cast<uint32_t>( encrypted.size() ) + 4 );
out.write_u32( static_cast<uint32_t>( m_symKey.size() ) );
out.write_bytes( encrypted );
}

View File

@@ -0,0 +1,38 @@
#include "RequestGetFriendList.h"
void RequestGetFriendList::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
}
sptr_generic_response RequestGetFriendList::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto publicKey = stream->read_utf8();
auto unknown = stream->read_u32();
return std::make_shared< ResultGetFriendList >( this );
}
ResultGetFriendList::ResultGetFriendList( GenericRequest *request ) : GenericResponse( *request )
{
}
void ResultGetFriendList::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
// Friends
out.write_u32(1);
out.write_utf16(L"String_1");
out.write_u32(1);
out.write_utf16(L"String_2");
// Ignore
out.write_u32(1);
out.write_utf16(L"String_3");
}

View File

@@ -0,0 +1,72 @@
#include "Network/Event/RequestGetGame.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "Game/RealmUser.hpp"
#include "logging.hpp"
void RequestGetGame::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_gameName = stream->read_utf16();
}
sptr_generic_response RequestGetGame::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", m_sessionId );
return std::make_shared< ResultGetGame >( this, TIMEOUT );
}
auto session = GameSessionManager::Get().FindGame( m_gameName, user->m_gameType );
if( session == nullptr )
{
Log::Error( "Game session not found! [{}]", m_gameName );
return std::make_shared< ResultGetGame >( this, NOT_FOUND );
}
if( session->m_currentPlayers >= session->m_maximumPlayers )
{
Log::Error( "Game session is full! [{}]", m_gameName );
return std::make_shared< ResultGetGame >( this, TIMEOUT );
}
auto host_user = session->GetOwner();
if( host_user == nullptr )
{
Log::Error( "Game session owner not found! [{}]", m_gameName );
return std::make_shared< ResultGetGame >( this, TIMEOUT );
}
user->m_isHost = false;
user->m_gameId = session->m_gameId;
return std::make_shared< ResultGetGame >( this, SUCCESS, session->m_gameId );
}
ResultGetGame::ResultGetGame( GenericRequest *request, int32_t reply, int32_t gameId ) : GenericResponse( *request )
{
m_reply = reply;
m_gameId = gameId;
}
void ResultGetGame::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
// TODO: These may come in from the UpdateGameData event
out.write_utf16( L"Kelethin" );
out.write_utf16( L"OwnerName" );
out.write_u32( m_gameId );
}

View File

@@ -0,0 +1,78 @@
#include "Network/Event/RequestGetGame_RTA.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "Game/RealmUser.hpp"
#include "logging.hpp"
void RequestGetGame_RTA::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_gameName = stream->read_utf16();
}
sptr_generic_response RequestGetGame_RTA::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", m_sessionId );
return std::make_shared< ResultGetGame_RTA >( this, TIMEOUT );
}
auto session = GameSessionManager::Get().FindGame( m_gameName, user->m_gameType );
if( session == nullptr )
{
Log::Error( "Game session not found! [{}]", m_gameName );
return std::make_shared< ResultGetGame_RTA >( this, NOT_FOUND );
}
if( session->m_currentPlayers >= session->m_maximumPlayers )
{
Log::Error( "Game session is full! [{}]", m_gameName );
return std::make_shared< ResultGetGame_RTA >( this, TIMEOUT );
}
auto host_user = session->GetOwner();
if( host_user == nullptr )
{
Log::Error( "Game session owner not found! [{}]", m_gameName );
return std::make_shared< ResultGetGame_RTA >( this, TIMEOUT );
}
user->m_isHost = false;
user->m_gameId = session->m_gameId;
return std::make_shared< ResultGetGame_RTA >( this, SUCCESS, session->m_gameId, host_user->m_discoveryAddr, host_user->m_localAddr, host_user->m_discoveryPort );
}
ResultGetGame_RTA::ResultGetGame_RTA( GenericRequest *request, int32_t reply, int32_t gameId, std::string discoveryAddr, std::string localAddr, int32_t discoveryPort ) : GenericResponse( *request )
{
m_reply = reply;
m_gameId = gameId;
m_discoveryAddr = Util::UTF8ToWide( discoveryAddr );
m_localAddr = Util::UTF8ToWide( localAddr );
m_port = discoveryPort;
}
void ResultGetGame_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
out.write_utf16( L"Test" );
out.write_u32( m_gameId );
out.write_utf16( m_discoveryAddr );
out.write_u32( m_port );
out.write_utf16( m_localAddr );
out.write_u32( m_port );
}

View File

@@ -0,0 +1,65 @@
#include "Network/Event/RequestGetNetCharacterList_RTA.hpp"
#include "Game/RealmUser.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/RealmCharacterMetaKV.hpp"
#include "Database/Database.hpp"
#include "logging.hpp"
void RequestGetNetCharacterList_RTA::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
}
sptr_generic_response RequestGetNetCharacterList_RTA::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( !user )
{
return std::make_shared< ResultGetNetCharacterList_RTA >( this, FATAL_ERROR, std::nullopt );
}
auto result = Database::Get().LoadCharacterSlots( user->m_accountId );
return std::make_shared< ResultGetNetCharacterList_RTA >( this, SUCCESS, result );
}
ResultGetNetCharacterList_RTA::ResultGetNetCharacterList_RTA( GenericRequest *request, int32_t reply, std::optional< std::map< uint32_t, CharacterSlotData > > list ) : GenericResponse( *request )
{
m_reply = reply;
if( list != std::nullopt )
{
m_characterList = list.value();
}
}
void ResultGetNetCharacterList_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
// Character Count
out.write_u32( static_cast< int >( m_characterList.size() ) );
// Character List
for( auto &character : m_characterList )
{
const auto &KV = character.second.GetMetaData();
// Character ID
out.write_u32( character.first );
// Number of Key-Value pairs
out.write_u32( static_cast< uint32_t >( KV.size() ) );
for( auto &pair : KV )
{
out.write_utf16( pair.first );
out.write_utf16( pair.second );
}
}
}

View File

@@ -0,0 +1,55 @@
#include "Network/Event/RequestGetPublicRooms.hpp"
#include "Game/ChatRoomManager.hpp"
void RequestGetPublicRooms::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
}
sptr_generic_response RequestGetPublicRooms::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
const auto publicRooms = ChatRoomManager::Get().GetPublicRoomList();
return std::make_shared< ResultGetPublicRooms >( this, publicRooms );
}
ResultGetPublicRooms::ResultGetPublicRooms( GenericRequest *request, std::vector< sptr_chat_room_session > rooms ) : GenericResponse( *request )
{
m_rooms = std::move( rooms );
}
void ResultGetPublicRooms::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
const auto numRoom = static_cast< uint32_t >( m_rooms.size() );
out.write_u32( numRoom );
for( const auto &room : m_rooms )
{
out.write_utf16( room->m_name );
}
out.write_u32( numRoom );
for( const auto &room : m_rooms )
{
out.write_utf16( L"UNKNOWN" );
}
out.write_u32( numRoom );
for( const auto &room : m_rooms )
{
out.write_u32( 88 );
}
out.write_u32( numRoom );
for( const auto &room : m_rooms )
{
out.write_u32( 99 );
}
}

View File

@@ -0,0 +1,39 @@
#include "Network/Event/RequestGetRealmStats.hpp"
#include "Game/RealmUserManager.hpp"
void RequestGetRealmStats::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
}
sptr_generic_response RequestGetRealmStats::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
return std::make_shared< ResultGetRealmStats >( this );
}
ResultGetRealmStats::ResultGetRealmStats( GenericRequest *request ) : GenericResponse( *request )
{
}
void ResultGetRealmStats::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
// Player count on the game page.
out.write_u32( UserManager::Get().GetUserCount() ); // Users Logged In Game
// I'm not sure this appears anywhere in the game.
out.write_u32( 0 ); // Users Logged In Realm
out.write_u32( 0 ); // Users Running Game
out.write_u32( 0 ); // Users Running Realm
out.write_u32( 0 ); // Users Playing Game
out.write_u32( 0 ); // Users Playing Realm
out.write_u32( 0 ); // unmatchedGamesGame
out.write_u32( 0 ); // unmatchedGamesRealm
}

View File

@@ -0,0 +1,86 @@
#include "Network/Event/RequestGetRoom.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/ChatRoomManager.hpp"
#include "logging.hpp"
void RequestGetRoom::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_roomName = stream->read_utf16();
}
sptr_generic_response RequestGetRoom::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
const auto user = UserManager::Get().FindUserBySocket( socket );
if( !user )
{
return std::make_shared< ResultGetRoom >( this, GENERAL_ERROR );
}
sptr_chat_room_session targetRoom = nullptr;
if( m_roomName.empty() )
{
targetRoom = ChatRoomManager::Get().FindRoom( user->m_privateRoomId );
}
else
{
targetRoom = ChatRoomManager::Get().FindRoom( user->m_publicRoomId );
}
return std::make_shared< ResultGetRoom >( this, SUCCESS, targetRoom );
}
ResultGetRoom::ResultGetRoom( GenericRequest *request, int32_t reply, sptr_chat_room_session room ) : GenericResponse( *request )
{
m_reply = reply;
m_room = room;
}
void ResultGetRoom::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
if( m_room )
{
out.write_utf16( m_room->m_name );
out.write_utf16( m_room->m_banner );
out.write_u32( static_cast< uint32_t >( m_room->m_members.size() ) );
for( const auto &m : m_room->m_members )
{
const auto &member = m.lock();
if( member )
{
out.write_utf16( member->m_chatHandle );
}
else
{
out.write_utf16( L"" );
}
}
out.write_u32( static_cast< uint32_t >( m_room->m_moderators.size() ) );
for( const auto &m : m_room->m_moderators )
{
if( auto member = m.lock() )
{
out.write_utf16( member->m_chatHandle );
}
else
{
out.write_utf16( L"" );
}
}
}
}

View File

@@ -0,0 +1,62 @@
#include "Network/Event/RequestGetRules.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/RealmUser.hpp"
#include "Common/Constant.hpp"
#include "logging.hpp"
void RequestGetRules::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_language = stream->read_sz_utf8();
}
sptr_generic_response RequestGetRules::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultGetRules >( this, L"" );
}
// TODO: Get rules/eula based on language
// and move it info a MOTD file.
std::wstring rules;
if( user->m_gameType == RealmGameType::RETURN_TO_ARMS )
{
rules = L"Welcome to the Champions Emulated Server!\n\n"
L"RETURN TO ARMS network support is currently a\n"
L"work in progress and can not guarantee stability.\n\n"
L"[IMPORTANT]:\n"
L"Please note that ONLINE character saves may be unstable.\n"
L"Use them at your own risk.\n";
}
else
{
rules = L"Welcome to the Champions Emulated Server!\n\n"
L"This server is currently in development\n"
L"and may not be fully functional.\n\n";
}
return std::make_shared< ResultGetRules >( this, rules );
}
ResultGetRules::ResultGetRules( GenericRequest *request, std::wstring rules ) : GenericResponse( *request )
{
m_rules = rules;
}
void ResultGetRules::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
out.write_utf16( m_rules );
}

View File

@@ -0,0 +1,47 @@
#include "Network/Event/RequestGetServerAddress.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/RealmUser.hpp"
#include "Common/Constant.hpp"
#include "configuration.hpp"
#include "logging.hpp"
void RequestGetServerAddress::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
}
sptr_generic_response RequestGetServerAddress::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
if( socket->gameType == RealmGameType::RETURN_TO_ARMS )
{
return std::make_shared< ResultGetServerAddress >( this, Config::service_ip, Config::rta_lobby_port, socket->gameType );
}
else
{
return std::make_shared< ResultGetServerAddress >( this, Config::service_ip, Config::con_lobby_port, socket->gameType );
}
}
ResultGetServerAddress::ResultGetServerAddress( GenericRequest *request, std::string ip, int32_t port, RealmGameType gameType ) : GenericResponse( *request )
{
m_ip = ip;
m_port = port;
m_gameType = gameType;
}
void ResultGetServerAddress::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
if( m_gameType == RealmGameType::RETURN_TO_ARMS )
out.write_utf8( m_ip );
else
out.write_sz_utf8( m_ip );
out.write( m_port );
}

View File

@@ -0,0 +1,54 @@
#include "Network/Event/RequestGetSocialListInitial.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/RealmUser.hpp"
#include "logging.hpp"
void RequestGetSocialListInitial::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
}
sptr_generic_response RequestGetSocialListInitial::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "RequestGetFriendList::ProcessRequest() - User not found" );
return std::make_shared< ResultGetSocialListInitial >( this );
}
return std::make_shared< ResultGetSocialListInitial >( this, user );
}
ResultGetSocialListInitial::ResultGetSocialListInitial( GenericRequest *request, sptr_user user ) : GenericResponse( *request )
{
m_user = user;
}
void ResultGetSocialListInitial::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
// Friends
out.write_u32( static_cast< uint32_t >( m_user->m_friendList.size() ) );
for( const auto &friendHandle : m_user->m_friendList )
{
out.write_utf16( friendHandle );
}
// ?
out.write_u32(1);
out.write_utf16(L"String_2");
// Ignore
out.write_u32( static_cast< uint32_t >( m_user->m_ignoreList.size() ) );
for( const auto &ignoreHandle : m_user->m_ignoreList )
{
out.write_utf16( ignoreHandle );
}
}

View File

@@ -0,0 +1,50 @@
#include "Network/Event/RequestGetSocialListUpdate.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/RealmUser.hpp"
#include "logging.hpp"
void RequestGetSocialListUpdate::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
}
sptr_generic_response RequestGetSocialListUpdate::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "RequestGetFriendList::ProcessRequest() - User not found" );
return std::make_shared< ResultGetSocialListUpdate >( this );
}
return std::make_shared< ResultGetSocialListUpdate >( this, user );
}
ResultGetSocialListUpdate::ResultGetSocialListUpdate( GenericRequest *request, sptr_user user ) : GenericResponse( *request )
{
m_user = user;
}
void ResultGetSocialListUpdate::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
// Friends
out.write_u32( static_cast< uint32_t >( m_user->m_friendList.size() ) );
for( const auto &friendHandle : m_user->m_friendList )
{
out.write_utf16( friendHandle );
}
// Ignore
out.write_u32( static_cast< uint32_t >( m_user->m_ignoreList.size() ) );
for( const auto &ignoreHandle : m_user->m_ignoreList )
{
out.write_utf16( ignoreHandle );
}
}

View File

@@ -0,0 +1,45 @@
#include "Network/Event/RequestLeaveRoom.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/ChatRoomManager.hpp"
#include "logging.hpp"
void RequestLeaveRoom::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
auto sessionId = stream->read_encrypted_utf16();
m_roomName = stream->read_utf16();
}
sptr_generic_response RequestLeaveRoom::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
const auto user = UserManager::Get().FindUserBySocket( socket );
if( !user )
{
Log::Error( "User not found for socket!" );
return std::make_shared< ResultLeaveRoom >( this, GENERAL_ERROR );
}
if( !ChatRoomManager::Get().LeaveRoom( user, m_roomName ) )
{
Log::Error( "Could not remove user [{}] from room [{}]", user->m_username, m_roomName );
return std::make_shared< ResultLeaveRoom >( this, GENERAL_ERROR );
}
return std::make_shared< ResultLeaveRoom >( this, SUCCESS );
}
ResultLeaveRoom::ResultLeaveRoom( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultLeaveRoom::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,122 @@
#include "Network/Event/RequestLogin.hpp"
#include "Database/Database.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/RealmUser.hpp"
#include "Common/Constant.hpp"
#include "logging.hpp"
void RequestLogin::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_username = stream->read_encrypted_utf16();
m_password = stream->read_encrypted_utf16();
}
sptr_generic_response RequestLogin::ProcessLoginCON( sptr_user user )
{
if( m_username != L"foo" && m_password != L"bar" )
{
// Network Beta CoN uses login information, but it's invalid because of version 2.0
// which used "foo" and "bar" as the login credentials.
Log::Debug( "RequestLogin : Champions of Norrath v1.0" );
// TODO: Either block this, or add support for the network beta.
return std::make_shared< ResultLogin >( this, LOGIN_REPLY::ACCOUNT_INVALID, L"" );
}
user->m_isLoggedIn = true;
user->m_accountId = -1;
user->m_sessionId = UserManager::Get().GenerateSessionId();
return std::make_shared< ResultLogin >( this, SUCCESS, user->m_sessionId );
}
sptr_generic_response RequestLogin::ProcessLoginRTA( sptr_user user )
{
// Return to Arms uses login information.
Log::Debug( "RequestLogin : Return to Arms" );
auto &UserManager = UserManager::Get();
auto &Database = Database::Get();
// Verify the account exists
auto [ result, accountId, chatHandle ] = Database.VerifyAccount( m_username, m_password );
if( accountId < 0 )
{
Log::Error( "RequestLogin::ProcessRequest() - Invalid account ID: " + std::to_string( accountId ) );
return std::make_shared< ResultLogin >( this, ACCOUNT_INVALID, L"" );
}
// Check if the user is already logged in
for( const auto &existingUser : UserManager.GetUserList() )
{
if( existingUser->m_username == m_username || existingUser->m_accountId == accountId )
{
return std::make_shared< ResultLogin >( this, FATAL_ERROR, L"" );
}
}
// Login Success
user->m_isLoggedIn = true;
user->m_username = m_username;
user->m_accountId = accountId;
user->m_chatHandle = chatHandle;
user->m_sessionId = UserManager.GenerateSessionId();
// Load Friend List
user->m_friendList = Database.LoadFriends( accountId );
// Load Ignore List
user->m_ignoreList = Database.LoadIgnores( accountId );
// Notify friends about the user's online status
UserManager.NotifyFriendsOnlineStatus( user, true );
return std::make_shared< ResultLogin >( this, SUCCESS, user->m_sessionId );
}
sptr_generic_response RequestLogin::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "RequestLogin::ProcessRequest() - User not found" );
return std::make_shared< ResultLogin >( this, ACCOUNT_INVALID, L"" );
}
if( m_username.empty() || m_password.empty() )
{
Log::Error( "RequestLogin::ProcessRequest() - Username or password is empty" );
return std::make_shared< ResultLogin >( this, ACCOUNT_INVALID, L"" );
}
if( user->m_gameType == RealmGameType::CHAMPIONS_OF_NORRATH )
{
return ProcessLoginCON( user );
}
else
{
return ProcessLoginRTA( user );
}
}
ResultLogin::ResultLogin( GenericRequest *request, int32_t reply, std::wstring sessionId ) : GenericResponse( *request )
{
m_reply = reply;
m_sessionId = sessionId;
}
void ResultLogin::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
out.write_encrypted_utf16( m_sessionId );
out.write_encrypted_utf16( L"UNKNOWN DUMMY STRING" );
}

View File

@@ -0,0 +1,33 @@
#include "Network/Event/RequestLogout.hpp"
#include "Game/RealmUserManager.hpp"
#include "logging.hpp"
void RequestLogout::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
}
sptr_generic_response RequestLogout::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
UserManager::Get().RemoveUser( socket );
Log::Debug( "[{}] Logout", m_sessionId );
return std::make_shared< ResultLogout >( this, 0 );
}
ResultLogout::ResultLogout( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultLogout::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,97 @@
#include "Network/Event/RequestMatchGame.hpp"
#include <format>
#include "Common/Constant.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "Game/RealmUser.hpp"
void RequestMatchGame::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
auto unknown_a = stream->read_u16();
auto unknown_b = stream->read_u32();
auto unknown_c = stream->read_u32();
auto unknown_d = stream->read_u32();
auto unknown_e = stream->read_u32();
// Match Game Node Count
for( uint32_t i = 0; i < unknown_e; i++ )
{
auto node_a = stream->read_u16();
auto node_b = stream->read_u32();
auto node_c = stream->read_utf16();
auto node_d = stream->read_u32();
auto node_e = stream->read_u32();
auto node_f = stream->read_u32();
auto node_g = stream->read_u16();
}
auto unknown_f = stream->read_u8();
auto unknown_g = stream->read_u32();
auto unknown_h = stream->read_u32();
}
sptr_generic_response RequestMatchGame::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
return std::make_shared< ResultMatchGame >( this, socket->remote_ip );
}
ResultMatchGame::ResultMatchGame( GenericRequest *request, std::string userIp ) : GenericResponse( *request )
{
m_userIp = userIp;
}
void ResultMatchGame::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
const auto publicGameList = GameSessionManager::Get().GetAvailableGameSessionList( RealmGameType::CHAMPIONS_OF_NORRATH );
const auto publicGameCount = static_cast< uint32_t >( publicGameList.size() );
out.write_u32( publicGameCount );
{
for( const auto &game : publicGameList )
{
if( m_userIp == game->m_hostExternalAddr )
out.write_utf16( std::format( L"{}:{}", Util::UTF8ToWide( game->m_hostLocalAddr ), game->m_hostNatPort ) );
else
out.write_utf16( std::format( L"{}:{}", Util::UTF8ToWide( game->m_hostExternalAddr ), game->m_hostNatPort ) );
}
}
out.write_u32( publicGameCount );
{
for( const auto &game : publicGameList )
out.write_utf16( game->m_gameName );
}
out.write_u32( publicGameCount );
{
for( const auto &game : publicGameList )
out.write_utf16( game->m_ownerName );
}
out.write_u32( publicGameCount );
{
for( const auto &game : publicGameList )
out.write_u32( game->m_gameId );
}
out.write_u32( publicGameCount );
{
for( const auto &game : publicGameList )
out.write_utf8( game->m_gameData );
}
}

View File

@@ -0,0 +1,93 @@
#include "Network/Event/RequestMatchGame_RTA.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "logging.hpp"
void RequestMatchGame_RTA::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
}
sptr_generic_response RequestMatchGame_RTA::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
UserManager::Get().Disconnect( socket, "User not found!" );
return std::make_shared< ResultMatchGame_RTA >( this, "" );
}
if( !user->m_isLoggedIn )
{
UserManager::Get().Disconnect( user, "User is not logged in!" );
return std::make_shared< ResultMatchGame_RTA >( this, "" );
}
return std::make_shared< ResultMatchGame_RTA >( this, socket->remote_ip );
}
ResultMatchGame_RTA::ResultMatchGame_RTA( GenericRequest *request, std::string userIp ) : GenericResponse( *request )
{
m_userIp = userIp;
}
void ResultMatchGame_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
const auto publicGameList = GameSessionManager::Get().GetAvailableGameSessionList( RealmGameType::RETURN_TO_ARMS );
const auto publicGameCount = static_cast< uint32_t >( publicGameList.size() );
out.write_u32( publicGameCount );
{
for( const auto &game : publicGameList )
out.write_utf16( Util::UTF8ToWide( game->m_gameData ) );
}
out.write_u32( publicGameCount );
{
for( const auto &game : publicGameList )
out.write_utf16( game->m_playerCount );
}
out.write_u32( publicGameCount );
{
for( const auto &game : publicGameList )
out.write_u32( game->m_gameId );
}
// Something about filtering.
out.write_u32( publicGameCount );
{
out.write_u32( 0 ); // Size
}
out.write_u32( publicGameCount );
{
for( const auto &game : publicGameList )
out.write_utf16( Util::UTF8ToWide( game->m_hostLocalAddr ) );
}
out.write_u32( publicGameCount );
{
for( const auto &game : publicGameList )
out.write_u32( game->m_hostLocalPort );
}
out.write_u32( publicGameCount );
{
for( const auto &game : publicGameList )
out.write_utf16( Util::UTF8ToWide( game->m_hostExternalAddr ) );
}
out.write_u32( publicGameCount );
{
for( const auto &game : publicGameList )
out.write_u32( game->m_hostNatPort );
}
}

View File

@@ -0,0 +1,56 @@
#include "Network/Event/RequestRemoveFriend.hpp"
#include "Game/RealmUserManager.hpp"
#include "Database/Database.hpp"
#include "logging.hpp"
void RequestRemoveFriend::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_chatHandle = stream->read_utf16();
}
sptr_generic_response RequestRemoveFriend::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultRemoveFriend >( this, FATAL_ERROR );
}
if( !user->IsFriend( m_chatHandle ) )
{
return std::make_shared< ResultRemoveFriend >( this, FRIEND_INVALID );
}
if( !Database::Get().RemoveFriend( user->m_accountId, m_chatHandle ) )
{
return std::make_shared< ResultRemoveFriend >( this, DATABASE_ERROR );
}
const auto iter = std::find( user->m_friendList.begin(), user->m_friendList.end(), m_chatHandle );
if( iter == user->m_friendList.end() )
{
return std::make_shared< ResultRemoveFriend >( this, FRIEND_INVALID );
}
user->m_friendList.erase( iter );
return std::make_shared< ResultRemoveFriend >( this, SUCCESS );
}
ResultRemoveFriend::ResultRemoveFriend( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultRemoveFriend::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,56 @@
#include "Network/Event/RequestRemoveIgnore.hpp"
#include "Game/RealmUserManager.hpp"
#include "Database/Database.hpp"
#include "logging.hpp"
void RequestRemoveIgnore::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_chatHandle = stream->read_utf16();
}
sptr_generic_response RequestRemoveIgnore::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultRemoveIgnore >( this, FATAL_ERROR );
}
if( !user->IsIgnored( m_chatHandle ) )
{
return std::make_shared< ResultRemoveIgnore >( this, IGNORE_INVALID );
}
if( !Database::Get().RemoveIgnore( user->m_accountId, m_chatHandle ) )
{
return std::make_shared< ResultRemoveIgnore >( this, DATABASE_ERROR );
}
const auto iter = std::find( user->m_ignoreList.begin(), user->m_ignoreList.end(), m_chatHandle );
if( iter == user->m_ignoreList.end() )
{
return std::make_shared< ResultRemoveIgnore >( this, IGNORE_INVALID );
}
user->m_ignoreList.erase( iter );
return std::make_shared< ResultRemoveIgnore >( this, SUCCESS );
}
ResultRemoveIgnore::ResultRemoveIgnore( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultRemoveIgnore::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,75 @@
#include "Network/Event/RequestSaveCharacter_RTA.hpp"
#include "Database/Database.hpp"
#include "Game/GameSessionManager.hpp"
#include "Game/CharacterSaveManager.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/RealmUser.hpp"
#include "Game/RealmCharacter.hpp"
#include "logging.hpp"
#include "Common/RLEZ.hpp"
void RequestSaveCharacter_RTA::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_memberSessionId = stream->read_encrypted_utf16();
m_targetCharacterId = stream->read_u32();
auto a = stream->read_u32();
auto b = stream->read_u32();
m_metaData.Deserialize( stream );
auto characterDataSize = stream->read_u32();
m_characterData = stream->read_bytes( characterDataSize );
}
sptr_generic_response RequestSaveCharacter_RTA::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto &userManager = UserManager::Get();
auto user = userManager.FindUserBySocket( socket );
if( user == nullptr || user->m_accountId == -1 )
{
return std::make_shared< ResultSaveCharacter_RTA >( this, FATAL_ERROR );
}
sptr_user targetUser = user;
if( m_sessionId != m_memberSessionId )
{
targetUser = userManager.FindUserBySessionId( m_memberSessionId );
}
if( targetUser == nullptr || targetUser->m_accountId == -1 )
{
Log::Error( "Target user not found or invalid account ID for session: {}", m_memberSessionId );
return std::make_shared< ResultSaveCharacter_RTA >( this, FATAL_ERROR );
}
auto characterId = targetUser->m_characterId;
auto &saveManager = CharacterSaveManager::Get();
if( saveManager.BeginSaveTask( user, targetUser, characterId, m_metaData, CharacterSaveType::SAVE_CHARACTER ) )
{
saveManager.AppendSaveData( user->m_sessionId, m_characterData, false );
}
return std::make_shared< ResultSaveCharacter_RTA >( this, SUCCESS );
}
ResultSaveCharacter_RTA::ResultSaveCharacter_RTA( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultSaveCharacter_RTA::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,54 @@
#include "Network/Event/RequestSendInstantMessage.hpp"
#include "Network\Event\NotifyInstantMessage.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/ChatRoomManager.hpp"
#include "logging.hpp"
void RequestSendInstantMessage::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
auto sessionId = stream->read_encrypted_utf16();
m_chatHandle = stream->read_utf16();
m_message = stream->read_utf16();
}
sptr_generic_response RequestSendInstantMessage::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
const auto user = UserManager::Get().FindUserBySocket( socket );
if( !user )
{
Log::Error( "User not found for socket!" );
return std::make_shared< ResultSendInstantMessage >( this, GENERAL_ERROR );
}
auto targetUser = UserManager::Get().FindUserByChatHandle( m_chatHandle );
if( !targetUser )
{
return std::make_shared< ResultSendInstantMessage >( this, GENERAL_ERROR );
}
if( targetUser->IsIgnored( user->m_chatHandle ) )
{
return std::make_shared< ResultSendInstantMessage >( this, USER_IGNORED );
}
targetUser->sock->send( NotifyInstantMessage( user->m_chatHandle, m_message ) );
return std::make_shared< ResultSendInstantMessage >( this, SUCCESS );
}
ResultSendInstantMessage::ResultSendInstantMessage( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultSendInstantMessage::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,72 @@
#include "Network/Event/RequestSendRoomMessage.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/ChatRoomManager.hpp"
#include "Network\Event\NotifyRoomMessage.hpp"
#include "logging.hpp"
void RequestSendRoomMessage::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
auto sessionId = stream->read_encrypted_utf16();
m_roomName = stream->read_utf16();
m_message = stream->read_utf16();
}
sptr_generic_response RequestSendRoomMessage::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
const auto user = UserManager::Get().FindUserBySocket( socket );
if( !user )
{
Log::Error( "User not found for socket!" );
return std::make_shared< ResultSendRoomMessage >( this, GENERAL_ERROR );
}
const auto room = ChatRoomManager::Get().FindRoom( m_roomName );
if( !room )
{
Log::Error( "Chat room not found: {}", m_roomName );
return std::make_shared< ResultSendRoomMessage >( this, GENERAL_ERROR );
}
if( !room->IsMember( user ) )
{
Log::Error( "User [{}] is not a member of chat room [{}]", user->m_chatHandle, m_roomName );
return std::make_shared< ResultSendRoomMessage >( this, GENERAL_ERROR );
}
NotifyRoomMessage msg( m_roomName, user->m_chatHandle, m_message );
for( const auto &member : room->m_members )
{
auto memberUser = member.lock();
if( !memberUser )
continue;
if( memberUser->IsIgnored( user->m_chatHandle ) )
{
continue; // Skip sending to ignored users
}
memberUser->sock->send( msg );
}
return std::make_shared< ResultSendRoomMessage >( this, SUCCESS );
}
ResultSendRoomMessage::ResultSendRoomMessage( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultSendRoomMessage::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,57 @@
#include "Network/Event/RequestStartGame.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "Game/ChatRoomManager.hpp"
#include "Game/RealmUser.hpp"
#include "logging.hpp"
void RequestStartGame::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
auto sessionId = stream->read_encrypted_utf16();
}
sptr_generic_response RequestStartGame::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultStartGame >( this, FATAL_ERROR );
}
auto session = GameSessionManager::Get().FindGame( user->m_gameId, user->m_gameType );
if( session == nullptr )
{
Log::Error( "Game session not found! [%d]", user->m_gameId );
return std::make_shared< ResultStartGame >( this, FATAL_ERROR );
}
if( !GameSessionManager::Get().RequestStart( user ) )
{
Log::Error( "Failed to start game session [{}]", user->m_gameId );
return std::make_shared< ResultStartGame >( this, FATAL_ERROR );
}
if( !ChatRoomManager::Get().CloseGameChatSession( session->m_gameName ) )
{
Log::Error( "Failed to close chat room for game session [{}]", session->m_gameName );
}
return std::make_shared< ResultStartGame >( this, SUCCESS );
}
ResultStartGame::ResultStartGame( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultStartGame::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,38 @@
#include "Network/Event/RequestTouchSession.hpp"
#include "Game/RealmUserManager.hpp"
#include "logging.hpp"
void RequestTouchSession::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
}
sptr_generic_response RequestTouchSession::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", m_sessionId );
return std::make_shared< ResultTouchSession >( this );
}
return std::make_shared< ResultTouchSession >( this );
}
ResultTouchSession::ResultTouchSession( GenericRequest *request ) : GenericResponse( *request )
{
}
void ResultTouchSession::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
}

View File

@@ -0,0 +1,91 @@
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4996)
#endif
#include "Network/Event/RequestUpdateGameData.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/GameSessionManager.hpp"
#include "Game/RealmUser.hpp"
#include "logging.hpp"
void RequestUpdateGameData::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_gameData = stream->read_utf8();
}
bool RequestUpdateGameData::ParseGameData( sptr_game_session session )
{
if( session == nullptr || m_gameData.size() < 256 )
{
Log::Error( "Invalid game session or game data size! [{}]", m_sessionId );
return false;
}
session->m_gameData = m_gameData;
int8_t currentPlayers = 0;
int8_t maxPlayers = 0;
char description[ 200 ] = { 0 };
int result = sscanf( m_gameData.c_str(), " %hhd / %hhd :%199[^\r\n]", &currentPlayers, &maxPlayers, description );
if( result >= 2 )
{
session->m_currentPlayers = currentPlayers;
session->m_maximumPlayers = maxPlayers;
session->m_description = ( result == 3 ) ? description : "";
}
else
{
Log::Debug( "Failed to parse game info from: {}", m_gameData );
}
return true;
}
sptr_generic_response RequestUpdateGameData::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
Log::Error( "User not found! [{}]", m_sessionId );
return std::make_shared< ResultUpdateGameData >( this );
}
auto gameSession = GameSessionManager::Get().FindGame( user->m_gameId, user->m_gameType );
if( !ParseGameData( gameSession ) )
{
Log::Error( "Failed to parse game data! [{}]", m_sessionId );
return std::make_shared< ResultUpdateGameData >( this );
}
const auto localAddr = std::string( m_gameData.c_str() + 220, 24 );
user->m_localAddr = localAddr;
return std::make_shared< ResultUpdateGameData >( this );
}
ResultUpdateGameData::ResultUpdateGameData( GenericRequest *request ) : GenericResponse( *request )
{
}
void ResultUpdateGameData::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( 0 );
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif

View File

@@ -0,0 +1,59 @@
#include "Network/Event/RequestUserJoinSuccess.hpp"
#include "Game/GameSessionManager.hpp"
#include "Game/RealmUserManager.hpp"
#include "Game/RealmUser.hpp"
#include "Database/Database.hpp"
#include "logging.hpp"
void RequestUserJoinSuccess::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_ownerSessionId = stream->read_encrypted_utf16();
}
sptr_generic_response RequestUserJoinSuccess::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto user = UserManager::Get().FindUserBySocket( socket );
if( user == nullptr )
{
return std::make_shared< ResultUserJoinSuccess >( this, FATAL_ERROR );
}
auto gameSession = GameSessionManager::Get().FindGame( user->m_gameId, user->m_gameType );
if( gameSession == nullptr )
{
Log::Error( "Game session not found for user: {}", user->m_username );
return std::make_shared< ResultUserJoinSuccess >( this, FATAL_ERROR );
}
if( !gameSession->IsJoinable() )
{
Log::Error( "Game session is not open for user: {}", user->m_username );
return std::make_shared< ResultUserJoinSuccess >( this, FATAL_ERROR );
}
if( !gameSession->AddMember( user ) )
{
Log::Error( "Failed to add user {} to game session {}", user->m_username, gameSession->m_gameId );
return std::make_shared< ResultUserJoinSuccess >( this, FATAL_ERROR );
}
return std::make_shared< ResultUserJoinSuccess >( this, SUCCESS );
}
ResultUserJoinSuccess::ResultUserJoinSuccess( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
void ResultUserJoinSuccess::Serialize( ByteBuffer &out ) const
{
out.write_u16( m_packetId );
out.write_u32( m_trackId );
out.write_u32( m_reply );
}

View File

@@ -0,0 +1,72 @@
#include "Request_5F.h"
#include "../../Game/GameSessionManager.h"
#include "../../Game/RealmUserManager.h"
#include "../../Game/RealmUser.h"
#include "../../Database/Database.h"
#include "../../logging.h"
void Request_5F::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
m_sessionId = stream->read_encrypted_utf16();
m_memberSessionId = stream->read_encrypted_utf16();
}
sptr_generic_response Request_5F::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
Log::Packet( stream->get_buffer(), stream->get_length(), false );
//auto user = UserManager::Get().FindUserBySocket( socket );
//if( user == nullptr )
//{
// return std::make_shared< Result_5F >( this, FATAL_ERROR );
//}
//
//auto targetUser = UserManager::Get().FindUserBySessionId( m_memberSessionId );
//if( targetUser == nullptr || targetUser->m_accountId == -1 )
//{
// Log::Error( "Target user not found or invalid account ID for session: %S", m_memberSessionId.c_str() );
// return std::make_shared< Result_5F >( this, FATAL_ERROR );
//}
//
//auto gameSession = GameSessionManager::Get().FindGame( user->m_gameId, user->m_gameType );
//if( gameSession == nullptr )
//{
// Log::Error( "Game session not found for user: %S", user->m_sessionId.c_str() );
// return std::make_shared< Result_5F >( this, FATAL_ERROR );
//}
//
//if( !gameSession->IsJoinable() )
//{
// Log::Error( "Game session is not open for user: %S", user->m_sessionId.c_str() );
// return std::make_shared< Result_5F >( this, FATAL_ERROR );
//}
//
//if( !gameSession->AddMember( targetUser ) )
//{
// Log::Error( "Failed to add user %S to game session %d", targetUser->m_sessionId.c_str(), gameSession->m_gameIndex );
// return std::make_shared< Result_5F >( this, FATAL_ERROR );
//}
// TODO: Here is more like finalize/confirm joined
return std::make_shared< Result_5F >( this, SUCCESS );
}
Result_5F::Result_5F( GenericRequest *request, int32_t reply ) : GenericResponse( *request )
{
m_reply = reply;
}
ByteBuffer& Result_5F::Serialize()
{
m_stream.write_u16( m_packetId );
m_stream.write_u32( m_trackId );
m_stream.write_u32( m_reply );
return m_stream;
}

View File

@@ -0,0 +1,40 @@
#include "Request_61.h"
void Request_61::Deserialize( sptr_byte_stream stream )
{
DeserializeHeader( stream );
}
sptr_generic_response Request_61::ProcessRequest( sptr_socket socket, sptr_byte_stream stream )
{
Deserialize( stream );
auto publicKey = stream->read_utf8();
auto unknown = stream->read_u32();
return std::make_shared< Result_61 >( this );
}
Result_61::Result_61( GenericRequest *request ) : GenericResponse( *request )
{
}
ByteBuffer&Result_61::Serialize()
{
m_stream.write_u16( m_packetId );
m_stream.write_u32( m_trackId );
m_stream.write_u32( 0 );
// Friends
m_stream.write_u32(1);
m_stream.write_utf16(L"String_1");
m_stream.write_u32(1);
m_stream.write_utf16(L"String_2");
// Ignore
m_stream.write_u32(1);
m_stream.write_utf16(L"String_3");
return m_stream;
}

View File

@@ -0,0 +1 @@
#include "Network/GenericNetRequest.hpp"

View File

@@ -0,0 +1,80 @@
#include "Network/RealmSocket.hpp"
#include "Common/Utility.hpp"
RealmSocket::RealmSocket()
{
fd = INVALID_SOCKET;
std::memset( &local_addr, 0, sizeof( local_addr ) );
std::memset( &remote_addr, 0, sizeof( remote_addr ) );
remote_ip = "";
remote_port = 0;
flag.disconnected_wait = 0;
flag.disconnected_forced = 0;
flag.is_listener = 0;
flag.is_gateway = 0;
flag.want_more_read_data = 0;
flag.want_more_write_data = 0;
last_write_position = 0;
last_recv_time = std::chrono::steady_clock::now();
last_send_time = std::chrono::steady_clock::now();
latency = 0;
}
RealmSocket::~RealmSocket()
{
if( INVALID_SOCKET != fd )
{
closesocket( fd );
}
fd = INVALID_SOCKET;
std::memset( &local_addr, 0, sizeof( local_addr ) );
std::memset( &remote_addr, 0, sizeof( remote_addr ) );
remote_ip = "";
remote_port = 0;
flag.disconnected_wait = 0;
flag.disconnected_forced = 0;
flag.is_listener = 0;
flag.is_gateway = 0;
flag.want_more_read_data = 0;
flag.want_more_write_data = 0;
last_write_position = 0;
latency = 0;
m_pendingWriteBuffer.reserve( WRITE_BUFFER_SIZE );
}
void RealmSocket::send( const sptr_generic_response response )
{
ByteBuffer stream;
response->Serialize( stream );
const auto netSize = Util::ByteSwap( static_cast< uint32_t >( stream.get_position() ) + 4 );
std::lock_guard< std::mutex > lock( write_mutex );
m_pendingWriteBuffer.insert( m_pendingWriteBuffer.end(), ( uint8_t * )&netSize, ( uint8_t * )&netSize + 4 );
m_pendingWriteBuffer.insert( m_pendingWriteBuffer.end(), stream.m_buffer.begin(), stream.m_buffer.end() );
}
void RealmSocket::send( const GenericMessage &message )
{
ByteBuffer stream;
message.Serialize( stream );
const auto netSize = Util::ByteSwap( static_cast< uint32_t >( stream.get_position() ) + 4 );
std::lock_guard< std::mutex > lock( write_mutex );
m_pendingWriteBuffer.insert( m_pendingWriteBuffer.end(), ( uint8_t * )&netSize, ( uint8_t * )&netSize + 4 );
m_pendingWriteBuffer.insert( m_pendingWriteBuffer.end(), stream.m_buffer.begin(), stream.m_buffer.end() );
}

54
Source/configuration.cpp Normal file
View File

@@ -0,0 +1,54 @@
#include "configuration.hpp"
bool Config::Load( std::string filename )
{
service_ip = "0.0.0.0";
con_lobby_port = 40900;
rta_lobby_port = 40910;
discovery_port = 10101;
// Read configuration from ini file
std::ifstream file( filename );
if( !file.is_open() )
{
return false;
}
std::string line;
while( std::getline( file, line ) )
{
if( line.empty() || line[0] == '#' || line[0] == ';' )
{
continue;
}
size_t pos = line.find( '=' );
if( pos == std::string::npos )
{
continue;
}
std::string key = line.substr( 0, pos );
std::string value = line.substr( pos + 1 );
if( key == "service_ip" )
{
service_ip = value;
}
else if( key == "con_lobby_port" )
{
con_lobby_port = std::stoi( value );
}
else if (key == "rta_lobby_port")
{
rta_lobby_port = std::stoi(value);
}
else if( key == "discovery_port" )
{
discovery_port = std::stoi( value );
}
}
return true;
}

191
Source/logging.cpp Normal file
View File

@@ -0,0 +1,191 @@
#include "logging.hpp"
static const char *LOG_PATH[] = {
"./generic",
"./debug",
"./error",
"./warning",
};
Log::Log()
{
current_open_hour = 0;
}
Log::~Log()
{
}
const char *Log::GetTimeStamp()
{
static char timestamp[ 64 ] = "";
time_t t; time( &t );
struct tm date_tm;
localtime_s( &date_tm, &t );
_snprintf_s( timestamp, _TRUNCATE, 63,
"[%4d-%02d-%02d %02d:%02d:%02d]: ",
( date_tm.tm_year + 1900 ),
( date_tm.tm_mon + 1 ),
date_tm.tm_mday,
date_tm.tm_hour,
date_tm.tm_min,
date_tm.tm_sec );
return timestamp;
}
void Log::CheckFileStatus( LOG_TYPE type )
{
time_t t; time( &t );
struct tm date_tm;
localtime_s( &date_tm, &t );
if( current_open_hour != date_tm.tm_hour )
{
current_open_hour = date_tm.tm_hour;
if( file_stream[ type ].is_open() )
{
file_stream[ type ].close();
}
}
if( !file_stream[ type ].is_open() )
{
char szFileName[ 256 ] = "";
_snprintf_s( szFileName, _TRUNCATE, 255, "./log/%s/log_%04d.%02d.%02d_%02d.log",
LOG_PATH[ type ],
( date_tm.tm_year + 1900 ),
( date_tm.tm_mon + 1 ),
date_tm.tm_mday,
date_tm.tm_hour );
file_stream[ type ].open( szFileName, std::fstream::out | std::fstream::app );
}
}
void Log::WriteToLog( LOG_TYPE type, const std::string &message )
{
std::lock_guard lock( log_lock );
HANDLE hConsole = GetStdHandle( STD_OUTPUT_HANDLE );
SetConsoleTextAttribute( hConsole, type + 10 );
switch( type )
{
case log_generic: printf( "[INFO]: " ); break;
case log_debug: printf( "[DEBUG]: " ); break;
case log_warn: printf( "[WARN]: " ); break;
case log_error: printf( "[ERROR]: " ); break;
}
SetConsoleTextAttribute( hConsole, 15 );
printf( "%s\n", message.c_str() );
CheckFileStatus( type );
file_stream[ type ] << GetTimeStamp() << message << '\n';
}
void Log::Packet( std::vector<uint8_t> p, size_t size, bool send )
{
#ifndef _DEBUG
return;
#endif
std::lock_guard<std::mutex> lock( log_lock );
HANDLE hConsole = GetStdHandle( STD_OUTPUT_HANDLE );
SetConsoleTextAttribute( hConsole, 15 );
uint16_t i = 0;
uint8_t line[ 16 ] = {};
uint8_t r = 0;
SetConsoleTextAttribute( hConsole, send ? 11 : 10 );
printf( "(%s)(00|01|02|03|04|05|06|07|08|09|0A|0B|0C|0D|0E|0F)\n", send ? "SEND" : "RECV" );
SetConsoleTextAttribute( hConsole, 15 );
while( i < size )
{
if( i % 16 == 0 )
{
if( i > 0 )
{
// Print ASCII characters for the previous line
printf( " " );
for( uint8_t j = 0; j < r; ++j )
{
char c = line[ j ];
printf( "%c", ( c >= 0x20 && c <= 0x7E ) ? c : '.' );
}
printf( "\n" );
}
printf( "(%04X) ", i );
r = 0;
}
line[ r++ ] = p[ i ];
// Highlight packet type or flags
if( i == 0 || i == 1 )
{
SetConsoleTextAttribute( hConsole, send ? 11 : 10 );
printf( "%02X ", p[ i ] );
SetConsoleTextAttribute( hConsole, 15 );
}
else
{
printf( "%02X ", p[ i ] );
}
++i;
}
// Padding for incomplete final line
if( r > 0 )
{
for( uint8_t j = r; j < 16; ++j )
printf( " " ); // Pad to align character section
printf( " " );
for( uint8_t j = 0; j < r; ++j )
{
char c = line[ j ];
printf( "%c", ( c >= 0x20 && c <= 0x7E ) ? c : '.' );
}
}
printf( "\n\n" );
}
void Log::ToFile( std::string prefix, std::vector<uint8_t> p, size_t size )
{
static char timestamp[ 64 ] = "";
time_t t; time( &t );
struct tm date_tm;
localtime_s( &date_tm, &t );
_snprintf_s( timestamp, _TRUNCATE, 63,
"%02d-%02d-%02d.packet.bin",
date_tm.tm_hour,
date_tm.tm_min,
date_tm.tm_sec );
std::string filename = prefix + '_' + timestamp;
std::fstream file;
file.open( filename, std::ios::out | std::ios::binary );
if( !file.is_open() )
{
Error( "Failed to open packet log file: %s", filename.c_str() );
return;
}
file.write( reinterpret_cast< const char * >( p.data() ), size );
file.close();
}

100
Source/main.cpp Normal file
View File

@@ -0,0 +1,100 @@
#include "stdafx.h"
#include <atomic>
#include <csignal>
#include <winsock2.h>
#include "logging.hpp"
#include "configuration.hpp"
#include "Database/Database.hpp"
#include "Lobby Server/LobbyServer.hpp"
#include "Discovery Server/DiscoveryServer.hpp"
std::atomic< bool > g_isRunning( true );
static void SignalHandler( int signal )
{
if( signal == SIGINT || signal == SIGTERM )
{
g_isRunning = false;
}
}
static void ShowStartup()
{
printf
(
"------------------------------------------------------\n"
"Champions Reborn | Server Build Version %s\n"
"------------------------------------------------------\n\n",
__DATE__
);
}
static bool NetworkStartup()
{
WORD wVersionRequest = MAKEWORD( 2, 2 );
WSADATA wsaData;
if( WSAStartup( wVersionRequest, &wsaData ) != 0 )
{
Log::Error( "WSAStartup() failed" );
return false;
}
return true;
}
int main()
{
ShowStartup();
std::signal( SIGINT, SignalHandler );
std::signal( SIGTERM, SignalHandler );
Log::Info( "Server Start..." );
if( !NetworkStartup() )
{
Log::Error( "Could not initialize network." );
return 0;
}
if( !Config::Load( "config.ini" ) )
{
Log::Error( "Failed to load configuration file." );
return 0;
}
auto &lobby_server = LobbyServer::Get();
lobby_server.Start( Config::service_ip );
auto &discovery_server = DiscoveryServer::Get();
discovery_server.Start( Config::service_ip, Config::discovery_port );
auto &database = Database::Get();
while( g_isRunning )
{
if( !lobby_server.isRunning() )
{
Log::Error( "Lobby Server is not running. Exiting." );
break;
}
if( !discovery_server.isRunning() )
{
Log::Error( "Discovery Server is not running. Exiting." );
break;
}
std::this_thread::sleep_for( std::chrono::milliseconds( 250 ) );
}
Log::Info( "Shutting down servers..." );
lobby_server.Stop();
discovery_server.Stop();
return 0;
}

8
Source/stdafx.cpp Normal file
View File

@@ -0,0 +1,8 @@
// stdafx.cpp : source file that includes just the standard includes
// Master Server.pch will be the pre-compiled header
// stdafx.obj will contain the pre-compiled type information
#include "stdafx.h"
// TODO: reference any additional headers you need in STDAFX.H
// and not in this file