1
Fork 0
mirror of https://github.com/SapphireServer/Sapphire.git synced 2025-04-26 14:37:44 +00:00
sapphire/src/lobby/GameConnection.cpp
2023-05-03 00:13:19 -04:00

520 lines
17 KiB
C++

#include "GameConnection.h"
#include <Common.h>
#include <Network/CommonNetwork.h>
#include <Util/Util.h>
#include <Logging/Logger.h>
#include <Network/GamePacket.h>
#include <Network/PacketDef/Lobby/ServerLobbyDef.h>
#include <Network/GamePacketParser.h>
#include <Crypt/md5.h>
#include <Crypt/blowfish.h>
#include <Config/ConfigMgr.h>
#include "ServerLobby.h"
#include "RestConnector.h"
#include "LobbySession.h"
#include "Forwards.h"
using namespace Sapphire;
using namespace Sapphire::Network::Packets;
using namespace Sapphire::Network::Packets::Server;
extern Lobby::ServerLobby g_serverLobby;
extern Lobby::RestConnector g_restConnector;
Lobby::GameConnection::GameConnection( Sapphire::Network::HivePtr pHive,
Sapphire::Network::AcceptorPtr pAcceptor ) :
Sapphire::Network::Connection( pHive ),
m_pAcceptor( pAcceptor ),
m_bEncryptionInitialized( false )
{
}
Lobby::GameConnection::~GameConnection()
{
}
// overwrite the parents onConnect for our game socket needs
void Lobby::GameConnection::onAccept( const std::string& host, uint16_t port )
{
auto connection = make_GameConnection( m_hive, m_pAcceptor );
m_pAcceptor->accept( connection );
Logger::info( "Connect from {0}", m_socket.remote_endpoint().address().to_string() );
}
void Lobby::GameConnection::onDisconnect()
{
Logger::debug( "DISCONNECT" );
}
void Lobby::GameConnection::onRecv( std::vector< uint8_t >& buffer )
{
m_packets.insert( std::end( m_packets ), std::begin( buffer ), std::end( buffer ) );
// This is assumed packet always start with valid FFXIVARR_PACKET_HEADER for now.
FFXIVARR_PACKET_HEADER packetHeader{};
const auto headerResult = getHeader( m_packets, 0, packetHeader );
if( headerResult == Incomplete )
return;
if( headerResult == Malformed )
{
Logger::info( "Dropping connection due to malformed packet header." );
disconnect();
return;
}
// Dissect packet list
std::vector< FFXIVARR_PACKET_RAW > packetList;
const auto packetResult = getPackets( m_packets, sizeof( struct FFXIVARR_PACKET_HEADER ),
packetHeader, packetList, nullptr );
if( packetResult == Incomplete )
return;
if( packetResult == Malformed )
{
Logger::info( "Dropping connection due to malformed packets." );
disconnect();
return;
}
// Handle it
handlePackets( packetHeader, packetList );
m_packets.clear();
}
void Lobby::GameConnection::onError( const asio::error_code& error )
{
Logger::info( "GameConnection closed: {0}", error.message() );
}
void
Lobby::GameConnection::sendError( uint64_t sequence, uint32_t errorcode, uint16_t messageId, uint32_t tmpId )
{
auto errorPacket = makeLobbyPacket< FFXIVIpcLobbyError >( tmpId );
errorPacket->data().seq = sequence;
errorPacket->data().error_id = errorcode;
errorPacket->data().message_id = messageId;
LobbyPacketContainer pRP( m_encKey );
pRP.addPacket( errorPacket );
sendPacket( pRP );
}
void Lobby::GameConnection::getCharList( FFXIVARR_PACKET_RAW& packet, uint32_t tmpId )
{
uint64_t sequence = *reinterpret_cast< uint64_t* >( &packet.data[ 0 ] + 0x10 );
Logger::info( "Sequence [{0}]", sequence );
Logger::info( "[{0}] ReqCharList", m_pSession->getAccountID() );
LobbyPacketContainer pRP( m_encKey );
auto serverListPacket = makeLobbyPacket< FFXIVIpcServerList >( tmpId );
serverListPacket->data().seq = 1;
serverListPacket->data().offset = 0;
serverListPacket->data().numServers = 1;
serverListPacket->data().server[ 0 ].id = g_serverLobby.getConfig().global.general.worldID;
serverListPacket->data().server[ 0 ].index = 0;
serverListPacket->data().final = 1;
strcpy( serverListPacket->data().server[ 0 ].name, g_serverLobby.getConfig().worldName.c_str() );
pRP.addPacket( serverListPacket );
auto retainerListPacket = makeLobbyPacket< FFXIVIpcRetainerList >( tmpId );
retainerListPacket->data().padding[ 8 ] = 1;
pRP.addPacket( retainerListPacket );
sendPacket( pRP );
auto charList = g_restConnector.getCharList( ( char* ) m_pSession->getSessionId() );
uint32_t charIndex = 0;
for( uint8_t i = 0; i < 4; i++ )
{
auto charListPacket = makeLobbyPacket< FFXIVIpcCharList >( tmpId );
charListPacket->data().seq = sequence;
charListPacket->data().numInPacket = 2;
charListPacket->data().counter = i * 4;
for( uint8_t j = 0; j < 2; j++ )
{
if( charIndex < charList.size() && charList.size() != 0 )
{
FFXIVIpcCharList::CharaDetails details;
memset( &details, 0, sizeof( FFXIVIpcCharList::CharaDetails ) );
auto& charEntry = charList[ charIndex ];
details.uniqueId = std::get< 1 >( charEntry );
details.contentId = std::get< 2 >( charEntry );
details.serverId = g_serverLobby.getConfig().global.general.worldID;
details.serverId1 = g_serverLobby.getConfig().global.general.worldID;
details.index = charIndex;
strcpy( details.charDetailJson, std::get< 3 >( charEntry ).c_str() );
strcpy( details.nameChara, std::get< 0 >( charEntry ).c_str() );
strcpy( details.nameServer, g_serverLobby.getConfig().worldName.c_str() );
strcpy( details.nameServer1, g_serverLobby.getConfig().worldName.c_str() );
charListPacket->data().charaDetails[ j ] = details;
Logger::debug( "[{0}] {1} - {2} - {3} - {4} - {5}",
charIndex,
details.index,
std::get< 0 >( charEntry ),
std::get< 1 >( charEntry ),
std::get< 2 >( charEntry ),
std::get< 3 >( charEntry ) );
}
charIndex++;
}
// TODO: Eventually move to account info storage
if( i == 3 )
{
charListPacket->data().entitledExpansion = Common::CURRENT_EXPANSION_ID;
charListPacket->data().maxCharOnWorld = 25;
charListPacket->data().unknown8 = 8;
charListPacket->data().veteranRank = 12;
charListPacket->data().counter = ( i * 4 ) + 1;
charListPacket->data().unknown4 = 128;
}
LobbyPacketContainer pRP( m_encKey );
pRP.addPacket( charListPacket );
sendPacket( pRP );
}
}
void Lobby::GameConnection::enterWorld( FFXIVARR_PACKET_RAW& packet, uint32_t tmpId )
{
uint64_t sequence = *reinterpret_cast< uint64_t* >( &packet.data[ 0 ] + 0x10 );
Logger::info( "Sequence [{0}]", sequence );
Logger::info( "[{0}] ReqEnterWorld", m_pSession->getAccountID() );
uint64_t lookupId = *reinterpret_cast< uint64_t* >( &packet.data[ 0 ] + 0x18 );
uint32_t logInCharId = -1;
std::string logInCharName;
auto charList = g_restConnector.getCharList( ( char* ) m_pSession->getSessionId() );
for( uint32_t i = 0; i < charList.size(); i++ )
{
uint64_t thisContentId = std::get< 2 >( charList[ i ] );
if( thisContentId == lookupId )
{
logInCharId = std::get< 1 >( charList[ i ] );
logInCharName = std::get< 0 >( charList[ i ] );
break;
}
}
if( logInCharId == -1 )
return;
Logger::info( "[{0}] Logging in as {1} ({2})", m_pSession->getAccountID(), logInCharName, logInCharId );
LobbyPacketContainer pRP( m_encKey );
auto enterWorldPacket = makeLobbyPacket< FFXIVIpcEnterWorld >( tmpId );
enterWorldPacket->data().contentId = lookupId;
enterWorldPacket->data().seq = sequence;
strcpy( enterWorldPacket->data().host, g_serverLobby.getConfig().global.network.zoneHost.c_str() );
enterWorldPacket->data().port = g_serverLobby.getConfig().global.network.zonePort;
enterWorldPacket->data().charId = logInCharId;
memcpy( enterWorldPacket->data().sid, m_pSession->getSessionId(), 66 );
pRP.addPacket( enterWorldPacket );
sendPacket( pRP );
}
bool Lobby::GameConnection::sendServiceAccountList( FFXIVARR_PACKET_RAW& packet, uint32_t tmpId )
{
LobbySessionPtr pSession = g_serverLobby.getSession( ( char* ) &packet.data[ 0 ] + 0x22 );
if( g_serverLobby.getConfig().allowNoSessionConnect && pSession == nullptr )
{
auto session = make_LobbySession();
session->setAccountID( 0 );
session->setSessionId( ( uint8_t* ) &packet.data[ 0 ] + 0x22 );
pSession = session;
Logger::info( "Allowed connection with no session: {0}", std::string( ( char* ) &packet.data[ 0 ] + 0x22 ) );
}
if( pSession != nullptr )
{
Logger::info( "Found session linked to accountId: {0}", pSession->getAccountID() );
m_pSession = pSession;
auto serviceIdInfoPacket = makeLobbyPacket< FFXIVIpcServiceIdInfo >( tmpId );
sprintf( serviceIdInfoPacket->data().serviceAccount[ 0 ].name, "FINAL FANTASY XIV" );
serviceIdInfoPacket->data().numServiceAccounts = 1;
serviceIdInfoPacket->data().u1 = 3;
serviceIdInfoPacket->data().u2 = 0x99;
serviceIdInfoPacket->data().serviceAccount[ 0 ].id = 0x002E4A2B;
LobbyPacketContainer pRP( m_encKey );
pRP.addPacket( serviceIdInfoPacket );
sendPacket( pRP );
}
else
{
Logger::info( "Could not retrieve session: {0}", std::string( ( char* ) &packet.data[ 0 ] + 0x22 ) );
sendError( 1, 5006, 13001, tmpId );
return true;
}
return false;
}
bool Lobby::GameConnection::createOrModifyChar( FFXIVARR_PACKET_RAW& packet, uint32_t tmpId )
{
uint64_t sequence = *reinterpret_cast< uint64_t* >( &packet.data[ 0 ] + 0x10 );
uint8_t type = *reinterpret_cast< uint8_t* >( &packet.data[ 0 ] + 0x29 );
Logger::info( "Sequence [{0}]", sequence );
Logger::info( "Type [{0}]", type );
Logger::info( "[{0}] ReqCharCreate", m_pSession->getAccountID() );
std::string name;
uint32_t newId = g_restConnector.getNextCharId();
uint64_t newContentId = g_restConnector.getNextContentId();
if( type == 1 ) //Character creation name check
{
name = std::string( ( char* ) &packet.data[ 0 ] + 0x2C );
Logger::info( "[{0}] Type 1: {1}", m_pSession->getAccountID(), name );
LobbyPacketContainer pRP( m_encKey );
m_pSession->newCharName = name;
if( g_restConnector.checkNameTaken( m_pSession->newCharName ) )
{
sendError( sequence, 3074, 13004, tmpId );
return true;
}
auto charCreatePacket = makeLobbyPacket< FFXIVIpcCharCreate >( tmpId );
charCreatePacket->data().content_id = newContentId;
strcpy( charCreatePacket->data().name, name.c_str() );
strcpy( charCreatePacket->data().world, g_serverLobby.getConfig().worldName.c_str() );
charCreatePacket->data().type = 1;
charCreatePacket->data().seq = sequence;
charCreatePacket->data().unknown = 1;
charCreatePacket->data().unknown_2 = 1;
charCreatePacket->data().unknown_7 = 1;
charCreatePacket->data().unknown_8 = 1;
pRP.addPacket( charCreatePacket );
sendPacket( pRP );
}
else if( type == 2 ) //Character creation finalize
{
std::string charDetails( ( char* ) &packet.data[ 0 ] + 0x4C );
Logger::info( "[{0}] Type 2: {1}", m_pSession->getAccountID(), charDetails );
if( g_restConnector.createCharacter( ( char* ) m_pSession->getSessionId(), m_pSession->newCharName, charDetails ) !=
-1 )
{
LobbyPacketContainer pRP( m_encKey );
auto charCreatePacket = makeLobbyPacket< FFXIVIpcCharCreate >( tmpId );
charCreatePacket->data().content_id = newContentId;
strcpy( charCreatePacket->data().name, name.c_str() );
strcpy( charCreatePacket->data().world, g_serverLobby.getConfig().worldName.c_str() );
strcpy( charCreatePacket->data().world2, g_serverLobby.getConfig().worldName.c_str() );
charCreatePacket->data().type = 2;
charCreatePacket->data().seq = sequence;
charCreatePacket->data().unknown = 1;
charCreatePacket->data().unknown_2 = 1;
charCreatePacket->data().unknown_7 = 1;
charCreatePacket->data().unknown_8 = 1;
pRP.addPacket( charCreatePacket );
sendPacket( pRP );
}
else
{
sendError( sequence, 5006, 13001, tmpId );
}
}
else if( type == 4 ) //Character delete
{
name = std::string( ( char* ) &packet.data[ 0 ] + 0x2C );
Logger::info( "[{0}] Type 4: {1}", m_pSession->getAccountID(), name );
if( g_restConnector.deleteCharacter( ( char* ) m_pSession->getSessionId(), name ) )
{
auto charCreatePacket = makeLobbyPacket< FFXIVIpcCharCreate >( tmpId );
//charCreatePacket->data().content_id = deletePlayer.getContentId();
charCreatePacket->data().content_id = 0;
strcpy( charCreatePacket->data().name, name.c_str() );
strcpy( charCreatePacket->data().world, g_serverLobby.getConfig().worldName.c_str() );
charCreatePacket->data().type = 4;
charCreatePacket->data().seq = sequence;
charCreatePacket->data().unknown = 1;
charCreatePacket->data().unknown_2 = 1;
charCreatePacket->data().unknown_7 = 1;
charCreatePacket->data().unknown_8 = 1;
LobbyPacketContainer pRP( m_encKey );
pRP.addPacket( charCreatePacket );
sendPacket( pRP );
}
else
{
sendError( sequence, 5006, 13001, tmpId );
}
}
else
{
Logger::error( "[{0}] Unknown Character Creation Type: {1}", m_pSession->getAccountID(), type );
}
return false;
}
void Lobby::GameConnection::handleGamePacket( Network::Packets::FFXIVARR_PACKET_RAW& packet )
{
uint32_t tmpId = packet.segHdr.target_actor;
Logger::info( "OpCode [{0}]", *reinterpret_cast< uint16_t* >( &packet.data[ 2 ] ) );
switch( *reinterpret_cast< uint16_t* >( &packet.data[ 2 ] ) )
{
case ClientVersionInfo:
{
// todo: validate client version based on sha1 or gamever/bootver
sendServiceAccountList( packet, tmpId );
}
break;
case ReqCharList:
{
getCharList( packet, tmpId );
}
break;
case ReqEnterWorld:
{
enterWorld( packet, tmpId );
}
break;
case ReqCharCreate:
{
createOrModifyChar( packet, tmpId );
}
break;
}
}
void Lobby::GameConnection::sendPacket( LobbyPacketContainer& pLpc )
{
uint16_t size = pLpc.getSize();
uint8_t* dataPtr = pLpc.getRawData( false );
std::vector< uint8_t > sendBuffer;
sendBuffer.assign( dataPtr, dataPtr + size );
send( sendBuffer );
}
void Lobby::GameConnection::sendPackets( Network::Packets::PacketContainer* pPacket )
{
std::vector< uint8_t > sendBuffer;
pPacket->fillSendBuffer( sendBuffer, nullptr );
send( sendBuffer );
}
void Lobby::GameConnection::sendSinglePacket( FFXIVPacketBasePtr pPacket )
{
PacketContainer pRP = PacketContainer();
pRP.addPacket( pPacket );
sendPackets( &pRP );
}
void Lobby::GameConnection::generateEncryptionKey( uint32_t key, const std::string& keyPhrase )
{
memset( m_baseKey, 0, 0x2C );
m_baseKey[ 0 ] = 0x78;
m_baseKey[ 1 ] = 0x56;
m_baseKey[ 2 ] = 0x34;
m_baseKey[ 3 ] = 0x12;
memcpy( m_baseKey + 0x04, &key, 4 );
m_baseKey[ 8 ] = 0xD4;
m_baseKey[ 9 ] = 0x17;
memcpy( ( char* ) m_baseKey + 0x0C, keyPhrase.c_str(), keyPhrase.size() );
Common::Util::md5( m_baseKey, m_encKey, 0x2C );
}
void Lobby::GameConnection::handlePackets( const Network::Packets::FFXIVARR_PACKET_HEADER& ipcHeader,
const std::vector< Network::Packets::FFXIVARR_PACKET_RAW >& packetData )
{
for( auto inPacket : packetData )
{
if( m_bEncryptionInitialized && inPacket.segHdr.type == 3 )
{
BlowFish blowfish;
blowfish.initialize( m_encKey, 0x10 );
blowfish.Decode( ( uint8_t* ) ( &inPacket.data[ 0 ] ), ( uint8_t* ) ( &inPacket.data[ 0 ] ),
( inPacket.data.size() ) - 0x10 );
}
switch( inPacket.segHdr.type )
{
case SEGMENTTYPE_ENCRYPTIONINIT: // Encryption init
{
std::string key_phrase( reinterpret_cast< char* >( &inPacket.data[ 36 ] ) );
generateEncryptionKey( *reinterpret_cast< uint32_t* >( &inPacket.data[ 100 ] ), key_phrase );
m_bEncryptionInitialized = true;
auto pe1 = std::make_shared< FFXIVRawPacket >( 0x0A, 0x290, 0, 0 );
*reinterpret_cast< uint32_t* >( &pe1->data()[ 0 ] ) = 0xE0003C2A;
BlowFish blowfish;
blowfish.initialize( m_encKey, 0x10 );
blowfish.Encode( &pe1->data()[ 0 ], &pe1->data()[ 0 ], 0x280 );
sendSinglePacket( pe1 );
break;
}
case SEGMENTTYPE_IPC: // game packet
{
Logger::info( "GamePacket [{0}]", inPacket.segHdr.type );
handleGamePacket( inPacket );
break;
}
case SEGMENTTYPE_KEEPALIVE: // keep alive
{
uint32_t id = *reinterpret_cast< uint32_t* >( &inPacket.data[ 0 ] );
uint32_t timeStamp = *reinterpret_cast< uint32_t* >( &inPacket.data[ 4 ] );
auto pe4 = std::make_shared< FFXIVRawPacket >( 0x08, 0x18, 0, 0 );
*( unsigned int* ) ( &pe4->data()[ 0 ] ) = id;
*( unsigned int* ) ( &pe4->data()[ 4 ] ) = timeStamp;
sendSinglePacket( pe4 );
break;
}
case 8:
{
break;
}
}
}
}