495 lines
15 KiB
C++
495 lines
15 KiB
C++
#include "pch.h"
|
|
#include "Log.h"
|
|
#include "l2c_utils.h"
|
|
#include "LS_Connection.h"
|
|
#include "GS.h"
|
|
#include "utils/Exception.h"
|
|
|
|
LoginConnection::LoginConnection()
|
|
{
|
|
m_ls_addr[0] = 0;
|
|
m_ls_port = m_ls_protover = 0;
|
|
m_isRunning = m_flagStop = m_isConnected = false;
|
|
m_sock = INVALID_SOCKET;
|
|
m_bfKey[0] = 0;
|
|
m_bfKeyLen = 0;
|
|
m_rsaKey = NULL;
|
|
m_registeredServerName[0] = 0;
|
|
m_registeredServerId = 0;
|
|
}
|
|
|
|
LoginConnection::~LoginConnection()
|
|
{
|
|
stop();
|
|
m_ls_addr[0] = 0;
|
|
m_ls_port = m_ls_protover = 0;
|
|
m_isRunning = m_flagStop = m_isConnected = false;
|
|
if( m_sock != INVALID_SOCKET )
|
|
{
|
|
L2PNet_shutdown( m_sock );
|
|
L2PNet_closesocket( m_sock );
|
|
}
|
|
m_sock = INVALID_SOCKET;
|
|
m_bfKey[0] = 0;
|
|
m_bfKeyLen = 0;
|
|
if( m_rsaKey )
|
|
{
|
|
RSA_free( m_rsaKey );
|
|
m_rsaKey = NULL;
|
|
}
|
|
m_registeredServerName[0] = 0;
|
|
m_registeredServerId = 0;
|
|
}
|
|
|
|
int LoginConnection::getRegisteredServerId() const
|
|
{
|
|
return m_registeredServerId;
|
|
}
|
|
|
|
const wchar_t *LoginConnection::getRegisteredServerName() const
|
|
{
|
|
return (const wchar_t *)m_registeredServerName;
|
|
}
|
|
|
|
void LoginConnection::start( const wchar_t *ls_addr, int ls_port, int ls_protover )
|
|
{
|
|
if( m_isRunning ) return;
|
|
HANDLE hThread = NULL;
|
|
unsigned int tid = 0;
|
|
m_flagStop = false;
|
|
m_lock.Lock();
|
|
{
|
|
hThread = (HANDLE)_beginthreadex( NULL, 0,
|
|
(unsigned int (__stdcall *)(void *))LoginConnection::LS_ConnThread, (void *)this,
|
|
0, &tid );
|
|
if( hThread )
|
|
{
|
|
wcsncpy( m_ls_addr, ls_addr, 127 ); m_ls_addr[127] = 0;
|
|
m_ls_port = ls_port;
|
|
m_ls_protover = ls_protover;
|
|
m_isRunning = true;
|
|
CloseHandle( hThread );
|
|
}
|
|
}
|
|
m_lock.Unlock();
|
|
}
|
|
|
|
void LoginConnection::stop()
|
|
{
|
|
if( !m_isRunning ) return;
|
|
m_lock.Lock();
|
|
m_flagStop = true;
|
|
while( m_isRunning ) Sleep(50);
|
|
m_flagStop = false;
|
|
m_registeredServerName[0] = 0;
|
|
m_registeredServerId = 0;
|
|
m_lock.Unlock();
|
|
//LogDebug( L"LS Connection stopped." ); // deadlock in SendMessage() :(
|
|
}
|
|
|
|
DWORD WINAPI LoginConnection::LS_ConnThread( LPVOID lpvParam )
|
|
{
|
|
LoginConnection *lc = (LoginConnection *)lpvParam;
|
|
//lc->m_isRunning = true; // not here, see line 107
|
|
unsigned char *packbuffer = NULL;
|
|
bool should_retry_connect = true;
|
|
int connect_tries = 0;
|
|
char a_ls_addr[128] = {0};
|
|
l2c_unicode_to_ansi( lc->m_ls_addr, a_ls_addr, 128 );
|
|
int bReadyRead = 0, bReadyWrite = 0, r = 0;
|
|
unsigned int rcvdLen = 0;
|
|
// initialize initial BF key
|
|
char initial_bfKey[] = "_;v.]05-31!|+-%xT!^[$\00";
|
|
unsigned int opcode = 0;
|
|
L2LoginPacket *pack = NULL;
|
|
|
|
retry_connect:
|
|
lc->m_isRunning = true; // only now we started work!
|
|
lc->m_isConnected = false; // not connected
|
|
connect_tries++;
|
|
if( connect_tries == 1 )
|
|
Log( L"Connecting to login at %s:%d...", lc->m_ls_addr, lc->m_ls_port );
|
|
else
|
|
Log( L"Connecting to login at %s:%d... (try #%d)", lc->m_ls_addr, lc->m_ls_port, connect_tries );
|
|
|
|
try
|
|
{
|
|
packbuffer = (unsigned char *)malloc( 64*1024 ); // 64 Kb enough
|
|
if( !packbuffer ) throw std::bad_alloc( "packbuffer (64 Kb)" );
|
|
|
|
lc->m_sock = L2PNet_TCPsocket_create( true );
|
|
L2PNet_connect( lc->m_sock, a_ls_addr, (unsigned short)lc->m_ls_port );
|
|
|
|
bReadyRead = 0, bReadyWrite = 0, r = 0;
|
|
r = L2PNet_select( lc->m_sock, L2PNET_SELECT_WRITE, 5000, &bReadyRead, &bReadyWrite );
|
|
if( !bReadyWrite ) throw Exception( "Connect to LS failed!" );
|
|
if( GameServer::getInstance()->getConfig()->Debug ) LogDebug( L"Connected to login." );
|
|
lc->m_isConnected = true; // we're connected!
|
|
|
|
// suddenly we're signaled to stop?
|
|
if( lc->m_flagStop ) goto lsconn_cleanup;
|
|
|
|
rcvdLen = 0;
|
|
memcpy( lc->m_bfKey, initial_bfKey, 23 );
|
|
lc->m_bfKey[22] = '0';
|
|
lc->m_bfKeyLen = 22;
|
|
|
|
while( !lc->m_flagStop )
|
|
{
|
|
r = L2PNet_select( lc->m_sock, L2PNET_SELECT_READ, 200, &bReadyRead, &bReadyWrite );
|
|
if( r == 0 ) continue; // timeout
|
|
if( r == -1 ) throw Exception( "Network select() error" );
|
|
r = L2PacketReceive_buffer( lc->m_sock, 2000, &rcvdLen, packbuffer );
|
|
if( r <= 0 ) throw Exception( "recv packet failed" );
|
|
// packet received OK
|
|
// decode blowfish
|
|
pack = new L2LoginPacket( packbuffer, rcvdLen );
|
|
pack->setDynamicBFKey( lc->m_bfKey, lc->m_bfKeyLen );
|
|
pack->decodeBlowfish( false );
|
|
// validate checksum
|
|
if( !pack->verifyChecksum() ) throw Exception( "Invalid packet checksum!" );
|
|
#ifdef _DEBUG
|
|
LogDebug( L"DBG: LoginConnection rcv: %u bytes (opcode %02X)", rcvdLen, (unsigned int)pack->getByteAt(2) );
|
|
#endif
|
|
// handle packet
|
|
opcode = (unsigned int)pack->getByteAt(2);
|
|
switch( opcode )
|
|
{
|
|
case 0x00: lc->ph_InitLS( pack ); break;
|
|
case 0x01: lc->ph_LoginServerFail( pack ); break;
|
|
case 0x02: lc->ph_LoginServerOK( pack ); break;
|
|
case 0x03: lc->ph_PlayerAuthResponse( pack ); break;
|
|
case 0x04: lc->ph_KickPlayer( pack ); break;
|
|
}
|
|
// free pack mem
|
|
if( pack ) delete pack;
|
|
pack = NULL;
|
|
}
|
|
}
|
|
catch( Exception& e )
|
|
{
|
|
if( !lc->m_flagStop ) // do not write log if signaled to stop (deadlock in SendMessage)
|
|
{
|
|
LogError( L"LoginConnection thread: Exception: %S", e.what() );
|
|
e.logStackTrace();
|
|
}
|
|
}
|
|
catch( std::bad_alloc& ba )
|
|
{
|
|
LogError( L"LoginConnection thread: bad memory allocation: %S", ba.what() );
|
|
}
|
|
catch( std::exception& e )
|
|
{
|
|
LogError( L"LoginConnection thread: error: %S", e.what() );
|
|
}
|
|
|
|
lsconn_cleanup:
|
|
// cleanup
|
|
if( packbuffer ) free( packbuffer );
|
|
packbuffer = NULL;
|
|
if( pack ) delete pack;
|
|
pack = NULL;
|
|
L2PNet_shutdown( lc->m_sock );
|
|
L2PNet_closesocket( lc->m_sock );
|
|
lc->m_sock = INVALID_SOCKET;
|
|
lc->m_bfKey[0] = 0;
|
|
lc->m_bfKeyLen = 0;
|
|
if( lc->m_rsaKey )
|
|
{
|
|
RSA_free( lc->m_rsaKey );
|
|
lc->m_rsaKey = NULL;
|
|
}
|
|
lc->m_registeredServerName[0] = 0;
|
|
lc->m_registeredServerId = 0;
|
|
lc->m_isRunning = false; // mark as stopped
|
|
lc->m_isConnected = false; // mark as not connected
|
|
|
|
// should we retry and connect again?
|
|
if( lc->m_flagStop ) should_retry_connect = false;
|
|
if( should_retry_connect )
|
|
{
|
|
if( GameServer::getInstance()->isRunning() )
|
|
should_retry_connect = true;
|
|
else
|
|
should_retry_connect = false;
|
|
}
|
|
if( should_retry_connect ) goto retry_connect;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
void LoginConnection::ph_InitLS( L2LoginPacket *pack )
|
|
{
|
|
// ddc[b64]
|
|
unsigned char rsa_modulus[128];
|
|
memset( rsa_modulus, 0, sizeof(rsa_modulus) );
|
|
//
|
|
pack->getPacketType(); // 0x00 - InitLS
|
|
int login_proto_rev = pack->readD(); // login protocol rev
|
|
unsigned int rsa_keylen = pack->readD() - 1; //
|
|
pack->readC(); // ?? wtf
|
|
pack->readBytes( rsa_modulus, rsa_keylen );
|
|
//
|
|
GameServer *gs = GameServer::getInstance();
|
|
if( login_proto_rev != gs->getConfig()->login_protocol_version )
|
|
LogWarning( L"Warning! login server sent protocol version %d (must be %d)",
|
|
login_proto_rev, gs->getConfig()->login_protocol_version );
|
|
if( gs->getConfig()->Debug )
|
|
LogDebug( L"LoginConnection: got InitLS, login protover = %u", login_proto_rev );
|
|
//
|
|
RSA *rsaKey = RSA_new();
|
|
BN_dec2bn( &(rsaKey->e), "65537" );
|
|
rsaKey->n = BN_bin2bn( rsa_modulus, rsa_keylen, NULL );
|
|
m_rsaKey = rsaKey;
|
|
|
|
send_BlowfishKey();
|
|
send_GameServerAuthRequest();
|
|
}
|
|
|
|
void LoginConnection::ph_LoginServerFail( L2LoginPacket *pack )
|
|
{
|
|
// c
|
|
pack->getPacketType();
|
|
unsigned int reason = (unsigned int)pack->readC();
|
|
bool mustThrow = true;
|
|
wchar_t strReason[32] = {0};
|
|
wcscpy( strReason, L"<unknown>" );
|
|
switch( reason )
|
|
{
|
|
case NOT_AUTHED: wcscpy( strReason, L"NOT_AUTHED" ); break;
|
|
case REASON_ALREADY_LOGGED_IN: wcscpy( strReason, L"REASON_ALREADY_LOGGED_IN" ); mustThrow = false; break;
|
|
case REASON_ID_RESERVED: wcscpy( strReason, L"REASON_ID_RESERVED" ); break;
|
|
case REASON_IP_BANNED: wcscpy( strReason, L"REASON_IP_BANNED" ); break;
|
|
case REASON_NO_FREE_ID: wcscpy( strReason, L"REASON_NO_FREE_ID" ); break;
|
|
case REASON_WRONG_HEXID: wcscpy( strReason, L"REASON_WRONG_HEXID" ); break;
|
|
}
|
|
if( mustThrow )
|
|
{
|
|
LogError( L"Failed to auth on login server! Error code %u (%s)", reason, strReason );
|
|
throw Exception( "LS auth failed!" );
|
|
}
|
|
LogWarning( L"Failed to auth on login server! Error code %u (%s)", reason, strReason );
|
|
}
|
|
|
|
void LoginConnection::ph_LoginServerOK( L2LoginPacket *pack )
|
|
{
|
|
// cS
|
|
pack->getPacketType();
|
|
m_registeredServerId = (int)pack->readUChar();
|
|
wcsncpy( m_registeredServerName, pack->readUnicodeStringPtr(), 255 );
|
|
m_registeredServerName[255] = 0;
|
|
Log( L"Registered on login as server #%d (%s)", m_registeredServerId, m_registeredServerName );
|
|
//
|
|
send_ServerStatus();
|
|
}
|
|
|
|
void LoginConnection::ph_PlayerAuthResponse( L2LoginPacket *pack )
|
|
{
|
|
// Sc
|
|
GameServer *gs = GameServer::getInstance();
|
|
pack->getPacketType();
|
|
wchar_t account[256] = {0};
|
|
wcsncpy( account, pack->readUnicodeStringPtr(), 255 ); account[255] = 0;
|
|
int ok = (int)pack->readUChar();
|
|
//
|
|
if( GameServer::getInstance()->getConfig()->Debug )
|
|
LogDebug( L"LS PlayerAuthResponse: account [%s] response: %d", account, ok );
|
|
//
|
|
gs->notifyLoginPlayerAuth( account, ok );
|
|
}
|
|
|
|
void LoginConnection::ph_KickPlayer( L2LoginPacket *pack )
|
|
{
|
|
// S
|
|
pack->getPacketType();
|
|
wchar_t account[256] = {0};
|
|
wcsncpy( account, pack->readUnicodeStringPtr(), 255 ); account[255] = 0;
|
|
//
|
|
GameServer *gs = GameServer::getInstance();
|
|
if( gs->getConfig()->Debug ) Log( L"LS KickPlayer acc [%s]", account );
|
|
gs->kickPlayerByAccount( account, true );
|
|
}
|
|
|
|
|
|
|
|
void LoginConnection::send_BlowfishKey()
|
|
{
|
|
// now reply with BlowfishKey
|
|
// construct array of 64 bytes, first 24 of them are NULLs, next 40 are new BF key.
|
|
unsigned char block[64];
|
|
unsigned char block_e[64];
|
|
memset( block, 0, sizeof(block) );
|
|
memset( block_e, 0, sizeof(block_e) );
|
|
srand( (unsigned int)time(NULL) );
|
|
int i;
|
|
for( i=0; i<40; i++ ) block[24+i] = (unsigned char)( (rand() % 255) & 0xFF );
|
|
|
|
// encode block with RSA key
|
|
int r = RSA_public_encrypt( 64, block, block_e, m_rsaKey, RSA_NO_PADDING );
|
|
if( r == -1 )
|
|
{
|
|
LogError( L"send BlowfishKey: RSA_public_encrypt() error!" );
|
|
m_flagStop = true;
|
|
return;
|
|
}
|
|
// construct packet BlowfishKey
|
|
L2LoginPacket p_bf;
|
|
p_bf.setPacketType( 0x00 );
|
|
p_bf.writeUInt( 64 ); // length of block
|
|
p_bf.writeBytes( block_e, 64 ); // rsa encrypted block with new BF key
|
|
|
|
// send it!
|
|
sendLSPacket( &p_bf );
|
|
|
|
// after send! save generated bf key as our new key
|
|
memcpy( m_bfKey, block+24, 40 );
|
|
m_bfKeyLen = 40;
|
|
|
|
// now we can free RSA key - we dont need it any more?
|
|
RSA_free( m_rsaKey );
|
|
m_rsaKey = NULL;
|
|
|
|
//
|
|
if( GameServer::getInstance()->getConfig()->Debug ) LogDebug( L"LoginConnection: sent BlowfishKey" );
|
|
}
|
|
|
|
void LoginConnection::send_GameServerAuthRequest()
|
|
{
|
|
GameServer *gs = GameServer::getInstance();
|
|
L2LoginPacket p;
|
|
p.setPacketType( 0x01 );
|
|
p.writeUChar( (unsigned char)(gs->getConfig()->server_id & 0xFF) );
|
|
p.writeUChar( gs->getConfig()->accept_alternate_server_id ? 0x01 : 0x00 );
|
|
p.writeUChar( 0x00 ); // host reserved = false
|
|
p.writeS( gs->getConfig()->report_external_hostname );
|
|
p.writeS( gs->getConfig()->report_internal_hostname );
|
|
p.writeH( (short)gs->getConfig()->game_server_bind_port );
|
|
p.writeD( gs->getConfig()->max_players );
|
|
// write hexid as byte array
|
|
wchar_t *server_hexid = gs->getConfig()->server_hexid;
|
|
int hexid_len = wcslen( server_hexid );
|
|
int i;
|
|
p.writeD( hexid_len/2 );
|
|
unsigned int val = 0;
|
|
wchar_t temp_ws[3] = {0,0,0};
|
|
for( i=0; i<hexid_len; i+=2 )
|
|
{
|
|
temp_ws[0] = server_hexid[i];
|
|
temp_ws[1] = server_hexid[i+1];
|
|
swscanf( temp_ws, L"%x", &val );
|
|
p.writeUChar( (unsigned char)(val & 0xFF) );
|
|
}
|
|
// send!
|
|
sendLSPacket( &p );
|
|
//
|
|
if( gs->getConfig()->Debug ) LogDebug( L"LoginConnection: sent GameServerAuthRequest" );
|
|
}
|
|
|
|
void LoginConnection::send_PlayerInGame( const wchar_t *accountName )
|
|
{
|
|
L2LoginPacket pack;
|
|
pack.setPacketType( 0x02 );
|
|
pack.writeH( 1 ); // 1 account name
|
|
pack.writeS( accountName );
|
|
sendLSPacket( &pack );
|
|
}
|
|
|
|
void LoginConnection::send_PlayerLogOut( const wchar_t *accountName )
|
|
{
|
|
L2LoginPacket pack;
|
|
pack.setPacketType( 0x03 );
|
|
pack.writeS( accountName );
|
|
sendLSPacket( &pack );
|
|
}
|
|
|
|
void LoginConnection::send_ChangeAccessLevel( const wchar_t *accountName, int new_access_level )
|
|
{
|
|
L2LoginPacket pack;
|
|
pack.setPacketType( 0x04 );
|
|
pack.writeD( new_access_level );
|
|
pack.writeS( accountName );
|
|
sendLSPacket( &pack );
|
|
}
|
|
|
|
void LoginConnection::send_PlayerAuthRequest( const wchar_t *accountName, unsigned char *loginKey, unsigned char *playKey )
|
|
{
|
|
L2LoginPacket pack;
|
|
pack.setPacketType( 0x05 );
|
|
pack.writeS( accountName );
|
|
pack.writeBytes( playKey, 8 );
|
|
pack.writeBytes( loginKey, 8 );
|
|
sendLSPacket( &pack );
|
|
}
|
|
|
|
void LoginConnection::send_ServerStatus()
|
|
{
|
|
GameServer *gs = GameServer::getInstance();
|
|
L2LoginPacket p;
|
|
p.setPacketType( 0x06 ); // opcode
|
|
//
|
|
p.writeD( 5 ); // parameters count
|
|
// first - server status
|
|
p.writeD( SERVER_LIST_STATUS );
|
|
p.writeD( gs->getServerStatus() );
|
|
// 2 - SERVER_LIST_CLOCK
|
|
p.writeD( SERVER_LIST_CLOCK );
|
|
p.writeD( gs->getConfig()->server_list_clock ? 0x01 : 0x00 );
|
|
// 3 - SERVER_LIST_SQUARE_BRACKET
|
|
p.writeD( SERVER_LIST_SQUARE_BRACKET );
|
|
p.writeD( gs->getConfig()->server_list_brackets ? 0x01 : 0x00 );
|
|
// 4 - max players
|
|
p.writeD( SERVER_LIST_MAX_PLAYERS );
|
|
p.writeD( gs->getMaxPlayers() );
|
|
// 5 - list as test server
|
|
p.writeD( SERVER_LIST_TEST_SERVER );
|
|
p.writeD( gs->getConfig()->list_as_test_server ? 0x01 : 0x00 );
|
|
//
|
|
sendLSPacket( &p );
|
|
}
|
|
|
|
void LoginConnection::sendLSPacket( L2LoginPacket *pack )
|
|
{
|
|
// asserts
|
|
if( !pack ) return;
|
|
if( m_sock == INVALID_SOCKET ) return; // socket down
|
|
if( !m_isRunning ) return; // login connection is stopped
|
|
if( !m_isConnected ) return; // maybe login connection thread is running, trying to connect, but not connected
|
|
//
|
|
unsigned int sentLen = 0;
|
|
L2LoginPacket *packet_copy = new L2LoginPacket( pack->getBytesPtr(), pack->getPacketSize() );
|
|
// pad packet to pad_step-byte border
|
|
const int pad_step = 8;
|
|
int dsize = packet_copy->getDataSize();
|
|
int rest = pad_step - (dsize % pad_step);
|
|
if( (rest > 0) && (rest < pad_step) ) // need alignment?
|
|
{
|
|
//LogDebug( L"Need padding %d bytes to %d byte-border", rest, pad_step );
|
|
int i;
|
|
for( i=0; i<rest; i++ ) packet_copy->writeC( 0x00 );
|
|
}
|
|
// now padded to 8-byte border; since we don't need to append more
|
|
// 4 bytes after checksum (appendChecksum( false )), write 4 bytes before chksum
|
|
// to keep padding to 8-byte border after checksum is appended
|
|
packet_copy->writeD( 0x00000000 );
|
|
// finally, checksum (4 bytes)
|
|
packet_copy->appendChecksum( false );
|
|
// now we can encode blowfish
|
|
packet_copy->setDynamicBFKey( m_bfKey, m_bfKeyLen );
|
|
packet_copy->encodeBlowfish( false );
|
|
//
|
|
m_lock.Lock();
|
|
int r = L2PacketSend( m_sock, packet_copy, 2000, &sentLen );
|
|
m_lock.Unlock();
|
|
if( r < 0 || sentLen != packet_copy->getPacketSize() )
|
|
{
|
|
//LogError( L"LoginConnection::sendLSPacket(): sent %u instead of %u bytes! (retval %d)",
|
|
// sentLen, packet_copy->getPacketSize(), r );
|
|
delete packet_copy;
|
|
throw Exception( "LoginConnection::sendLSPacket(): sent %u instead of %u bytes! (retval %d)",
|
|
sentLen, packet_copy->getPacketSize(), r );
|
|
}
|
|
delete packet_copy;
|
|
}
|