From da906586eabbd38e19c88a26e2fa7631e12f94a8 Mon Sep 17 00:00:00 2001 From: goaaats Date: Mon, 8 Jan 2018 21:42:44 +0100 Subject: [PATCH] Added basic capture replay --- .../DebugCommand/DebugCommandHandler.cpp | 61 +++++++++++++++--- .../DebugCommand/DebugCommandHandler.h | 1 + .../sapphire_zone/Network/GameConnection.cpp | 8 +-- src/servers/sapphire_zone/Session.cpp | 64 +++++++++++++++++++ src/servers/sapphire_zone/Session.h | 6 ++ 5 files changed, 128 insertions(+), 12 deletions(-) diff --git a/src/servers/sapphire_zone/DebugCommand/DebugCommandHandler.cpp b/src/servers/sapphire_zone/DebugCommand/DebugCommandHandler.cpp index 4c07b74b..ad448772 100644 --- a/src/servers/sapphire_zone/DebugCommand/DebugCommandHandler.cpp +++ b/src/servers/sapphire_zone/DebugCommand/DebugCommandHandler.cpp @@ -45,16 +45,17 @@ extern Core::ServerZone g_serverZone; Core::DebugCommandHandler::DebugCommandHandler() { // Push all commands onto the register map ( command name - function - description - required GM level ) - registerCommand( "set", &DebugCommandHandler::set, "Loads and injects a premade Packet.", 1 ); - registerCommand( "get", &DebugCommandHandler::get, "Loads and injects a premade Packet.", 1 ); - registerCommand( "add", &DebugCommandHandler::add, "Loads and injects a premade Packet.", 1 ); + registerCommand( "set", &DebugCommandHandler::set, "Executes SET commands.", 1 ); + registerCommand( "get", &DebugCommandHandler::get, "Executes GET commands.", 1 ); + registerCommand( "add", &DebugCommandHandler::add, "Executes ADD commands.", 1 ); registerCommand( "inject", &DebugCommandHandler::injectPacket, "Loads and injects a premade packet.", 1 ); registerCommand( "injectc", &DebugCommandHandler::injectChatPacket, "Loads and injects a premade chat packet.", 1 ); - registerCommand( "nudge", &DebugCommandHandler::nudge, "Nudges you forward/up/down", 1 ); - registerCommand( "info", &DebugCommandHandler::serverInfo, "Send server info", 0 ); - registerCommand( "unlock", &DebugCommandHandler::unlockCharacter, "Unlock character", 1 ); - registerCommand( "help", &DebugCommandHandler::help, "Shows registered commands", 0 ); - registerCommand( "script", &DebugCommandHandler::script, "Server script utilities", 1 ); + registerCommand( "replay", &DebugCommandHandler::replay, "Replays a saved capture folder.", 1 ); + registerCommand( "nudge", &DebugCommandHandler::nudge, "Nudges you forward/up/down.", 1 ); + registerCommand( "info", &DebugCommandHandler::serverInfo, "Show server info.", 0 ); + registerCommand( "unlock", &DebugCommandHandler::unlockCharacter, "Unlock character.", 1 ); + registerCommand( "help", &DebugCommandHandler::help, "Shows registered commands.", 0 ); + registerCommand( "script", &DebugCommandHandler::script, "Server script utilities.", 1 ); } // clear all loaded commands @@ -455,6 +456,50 @@ void Core::DebugCommandHandler::injectChatPacket( char * data, Entity::Player& p pSession->getChatConnection()->injectPacket( data + 8, player ); } +void Core::DebugCommandHandler::replay( char * data, Entity::Player& player, boost::shared_ptr< DebugCommand > command ) +{ + std::string subCommand; + std::string params = ""; + + // check if the command has parameters + std::string tmpCommand = std::string( data + command->getName().length() + 1 ); + + std::size_t pos = tmpCommand.find_first_of( " " ); + + if( pos != std::string::npos ) + // command has parameters, grab the first part + subCommand = tmpCommand.substr( 0, pos ); + else + // no subcommand given + subCommand = tmpCommand; + + if( command->getName().length() + 1 + pos + 1 < strlen( data ) ) + params = std::string( data + command->getName().length() + 1 + pos + 1 ); + + g_log.debug( "[" + std::to_string( player.getId() ) + "] " + + "subCommand " + subCommand + " params: " + params ); + + + if( subCommand == "start" ) + { + auto pSession = g_serverZone.getSession( player.getId() ); + if( pSession ) + pSession->startReplay( params ); + } + else if( subCommand == "stop" ) + { + auto pSession = g_serverZone.getSession( player.getId() ); + if( pSession ) + pSession->stopReplay(); + } + else + { + player.sendUrgent( subCommand + " is not a valid replay command." ); + } + + +} + void Core::DebugCommandHandler::nudge( char * data, Entity::Player& player, boost::shared_ptr< DebugCommand > command ) { std::string subCommand; diff --git a/src/servers/sapphire_zone/DebugCommand/DebugCommandHandler.h b/src/servers/sapphire_zone/DebugCommand/DebugCommandHandler.h index 930da36b..cc5570e5 100644 --- a/src/servers/sapphire_zone/DebugCommand/DebugCommandHandler.h +++ b/src/servers/sapphire_zone/DebugCommand/DebugCommandHandler.h @@ -38,6 +38,7 @@ public: void injectPacket( char * data, Entity::Player& player, boost::shared_ptr< DebugCommand > command ); void injectChatPacket( char * data, Entity::Player& player, boost::shared_ptr< DebugCommand > command ); + void replay( char * data, Entity::Player& player, boost::shared_ptr< DebugCommand > command ); void nudge( char* data, Entity::Player& player, boost::shared_ptr< DebugCommand > command ); void serverInfo( char * data, Entity::Player& player, boost::shared_ptr< DebugCommand > command ); diff --git a/src/servers/sapphire_zone/Network/GameConnection.cpp b/src/servers/sapphire_zone/Network/GameConnection.cpp index 817a2278..50acbf3f 100644 --- a/src/servers/sapphire_zone/Network/GameConnection.cpp +++ b/src/servers/sapphire_zone/Network/GameConnection.cpp @@ -307,7 +307,7 @@ void Core::Network::GameConnection::sendSinglePacket( Packets::GamePacket* pPack sendPackets( &pRP ); } -void Core::Network::GameConnection::injectPacket( const std::string& packetpath, Core::Entity::Player& pPlayer ) +void Core::Network::GameConnection::injectPacket( const std::string& packetpath, Core::Entity::Player& player ) { char packet[0x11570]; @@ -318,7 +318,7 @@ void Core::Network::GameConnection::injectPacket( const std::string& packetpath, fp = fopen( packetpath.c_str(), "rb" ); if( fp == nullptr ) { - g_log.error( "Packet " + packetpath + " not found!" ); + player.sendDebug( "Packet " + packetpath + " not found!" ); return; } @@ -328,7 +328,7 @@ void Core::Network::GameConnection::injectPacket( const std::string& packetpath, rewind( fp ); if ( fread( packet, sizeof( char ), size, fp ) != size ) { - g_log.error( "Packet " + packetpath + " did not read full size: " + std::to_string( size ) ); + player.sendDebug( "Packet " + packetpath + " did not read full size: " + std::to_string( size ) ); return; } fclose( fp ); @@ -336,7 +336,7 @@ void Core::Network::GameConnection::injectPacket( const std::string& packetpath, // cycle through the packet entries and queue each one for( int32_t k = 0x18; k < size; ) { - uint32_t tmpId = pPlayer.getId(); + uint32_t tmpId = player.getId(); // replace ids in the entryheader if needed if( !memcmp( packet + k + 0x04, packet + k + 0x08, 4 ) ) { diff --git a/src/servers/sapphire_zone/Session.cpp b/src/servers/sapphire_zone/Session.cpp index bef1b516..69a41237 100644 --- a/src/servers/sapphire_zone/Session.cpp +++ b/src/servers/sapphire_zone/Session.cpp @@ -6,6 +6,10 @@ #include "Session.h" #include "Actor/Player.h" +#include +#include + +extern Core::Logger g_log; Core::Session::Session( uint32_t sessionId ) : m_sessionId( sessionId ) @@ -107,8 +111,68 @@ void Core::Session::updateLastSqlTime() m_lastSqlTime = static_cast< uint32_t >( Util::getTimeSeconds() ); } +void Core::Session::startReplay( const std::string& folderpath ) +{ + if( !boost::filesystem::exists(folderpath) ) + { + getPlayer()->sendDebug( "Couldn't find folder." ); + return; + } + + m_replayCache.clear(); + + std::vector> loadedSets; + + for( auto it = boost::filesystem::directory_iterator( boost::filesystem::path( folderpath ) ); it != boost::filesystem::directory_iterator(); ++it ) + { + // Get the filename of the current element + auto filename = it->path().filename().string(); + auto unixtime = atoi( filename.substr( 0, 10 ).c_str() ); + + if( unixtime > 1000000000) + { + loadedSets.push_back( std::tuple( unixtime, it->path().string() ) ); + } + } + + sort( loadedSets.begin(), loadedSets.end(), [ ]( const std::tuple& left, const std::tuple& right) + { + return std::get<0>( left ) < std::get<0>( right ); + } ); + + int startTime = std::get<0>( loadedSets.at( 0 ) ); + + for( auto thing : loadedSets ) + { + m_replayCache.push_back( std::tuple( Util::getTimeSeconds() + ( std::get<0>( thing ) - startTime ), std::get<1>( thing ) ) ); + g_log.info( "Registering " + std::get<1>( thing ) + " for " + std::to_string( std::get<0>( thing ) - startTime ) ); + } + + getPlayer()->sendDebug( "Registered " + std::to_string( m_replayCache.size() ) + " sets for replay" ); + m_isReplaying = true; +} + +void Core::Session::stopReplay() +{ + m_isReplaying = false; + m_replayCache.clear(); +} + void Core::Session::update() { + if( m_isReplaying ) + { + int at = 0; + for( auto const& set : m_replayCache ) { + if( std::get<0>( set ) == Util::getTimeSeconds() ) + { + m_pZoneConnection->injectPacket( std::get<1>( set ), *getPlayer().get() ); + m_replayCache.erase( m_replayCache.begin() + at ); + } + at++; + } + } + if( m_pZoneConnection ) { m_pZoneConnection->processInQueue(); diff --git a/src/servers/sapphire_zone/Session.h b/src/servers/sapphire_zone/Session.h index 7145c1e9..95f5b69d 100644 --- a/src/servers/sapphire_zone/Session.h +++ b/src/servers/sapphire_zone/Session.h @@ -26,6 +26,9 @@ namespace Core { void updateLastDataTime(); void updateLastSqlTime(); + void startReplay( const std::string& folderpath ); + void stopReplay(); + void close(); uint32_t getId() const; @@ -48,6 +51,9 @@ namespace Core { uint32_t m_lastSqlTime; bool m_isValid; + bool m_isReplaying; + std::vector> m_replayCache; + Network::GameConnectionPtr m_pZoneConnection; Network::GameConnectionPtr m_pChatConnection;