• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

Protocol Version 1101

jo3bingham

Excellent OT User
Joined
Mar 3, 2008
Messages
1,103
Solutions
14
Reaction score
772
GitHub
jo3bingham
With yesterday's update, CipSoft added something odd to their connection protocol. When you select a character from the character list, the client sends the name of the selected character's world to the game server. The world name is a UTF8, newline-terminated string (e.g., "Antica\n"), and the packet only contains this string. No packet length, no opcode, no string length.

For those who don't know the connection protocol, here's a simplified explanation of how it worked before the update: the client connects to one of the login servers and passes your account information to it, the login server responds with a list of your characters and the worlds they belong to, then the client connects to the game server of the world of the character you selected but does not send any data to it. When the connection is made, the game server sends a LoginChallenge packet to the client, and the client responds with your selected character.

With this update, the protocol now works like this: the client connects to one of the login servers and passes your account information to it, the login server responds with a list of your characters and the worlds they belong to, then the client connects to the game server of the world of the character you selected. When the connection is made, the client sends the name of the selected character's world to the game server. Then the game server sends a LoginChallenge packet to the client, and the client responds with your selected character.

Edit - The string was originally null-terminated (\0), now it is newline-terminated (\n).
 
Last edited:
Uhmm it seems like new protocolgame class should be implemented in TFS which would extend existing protocolgame class and replace parseFirstPacket a login methods. Have you also done some research for payloads ?
 
So, basically what you're saying and laying down and also unfolding here is that in the end its about the fact that we have to navigate the C++ code into the space-time-magnifier so the fundamental paralell diffusion won't work, unless - you're using ports 7171.. but everyone is, so this whole thread is pointless.

Thanks for info though
 
My findings show that it's actually \n terminated. Thanks for the post though. Wonder what's the reasoning behind this change, maybe they have some text-rules-based firewall/loadbalancer in front of game servers now.
 
My findings show that it's actually \n terminated. Thanks for the post though. Wonder what's the reasoning behind this change, maybe they have some text-rules-based firewall/loadbalancer in front of game servers now.
You can actually see the reason quite easily, check the json the server returns on login and you'll see that all the gameservers is actually just a proxy address, this new string simply just lets the proxy know which server it's supposed to be heading to.

This atleast is my conclusion of all this.
 
@Milice

yes this seems like a reason.

I successfully updated the protocol in TFS by editing connection.cpp to accept bytes till '\n' is received. However, I am still figuring out how to make it dynamic by a world name (now the first 2 chars of world name is checked with first 2 chars of header). Is there some function to get port number of the connection ?
 
Uhmm it seems like new protocolgame class should be implemented in TFS which would extend existing protocolgame class and replace parseFirstPacket a login methods. Have you also done some research for payloads ?
I have not.
You can actually see the reason quite easily, check the json the server returns on login and you'll see that all the gameservers is actually just a proxy address, this new string simply just lets the proxy know which server it's supposed to be heading to.

This atleast is my conclusion of all this.
When I first wrote this post, CipSoft was still using per-server load balancers (e.g., antica-lb.ciproxy.com). Now they're using region-based load balancers (e.g., tibia-pool-us.ciproxy.com). You can see why I was confused before, but now it makes sense.
 
@Milice

yes this seems like a reason.

I successfully updated the protocol in TFS by editing connection.cpp to accept bytes till '\n' is received. However, I am still figuring out how to make it dynamic by a world name (now the first 2 chars of world name is checked with first 2 chars of header). Is there some function to get port number of the connection ?
You could probably get this somehow checking the protocol variable, although i have written an ugly fix for this (it works, but there are probably way better ways to accomplish this)

This is what it looks like. I'm pretty sure every c++ developer on here would cry their eyes out.
C++:
void Connection::parseHeader(const boost::system::error_code& error)
{
   if(!receivedServerName) {
       uint8_t* msgBuffer = msg.getBuffer();
       std::string serverName = g_config.getString(ConfigManager::SERVER_NAME);

       if(receivedServerNameFirst == false && !(((char)msgBuffer[0] == serverName[0]) && ((char)msgBuffer[1] == serverName[1]))) {
           receivedServerName = true;
           receivedServerNameFirst = true;
       } else {
           if(!receivedServerNameFirst) {
               receivedServerNameFirst = true;
           }

           if( (((char)msgBuffer[0] == serverName[serverName.length()-2]) && ((char)msgBuffer[1] == serverName[serverName.length()-1])) || (((char)msgBuffer[0] == serverName[serverName.length()-1]) && (msgBuffer[1] == 0x0A)) ) {
               receivedServerName = true;
               protocol->onRecvServerMessage();
           }

           try {
                   readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
                   readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                                 std::placeholders::_1));

               // Wait to the next packet
                   boost::asio::async_read(socket,
                                           boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH),
                                          std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
               } catch (boost::system::system_error& e) {
                   std::cout << "[Network error - Connection::parsePacket] " << e.what() << std::endl;
                   close(FORCE_CLOSE);
               }
           return;
       }
   }
 
Last edited by a moderator:
My solution without server name is

.cpp
C++:
/**
 * The Forgotten Server - a free and open-source MMORPG server emulator
 * Copyright (C) 2017  Mark Samman <[email protected]>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "otpch.h"

#include "configmanager.h"
#include "connection.h"
#include "outputmessage.h"
#include "protocol.h"
#include "protocolgame.h"
#include "scheduler.h"
#include "server.h"

extern ConfigManager g_config;

Connection_ptr ConnectionManager::createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort)
{
    std::lock_guard<std::mutex> lockClass(connectionManagerLock);

    auto connection = std::make_shared<Connection>(io_service, servicePort);
    connections.insert(connection);
    return connection;
}

void ConnectionManager::releaseConnection(const Connection_ptr& connection)
{
    std::lock_guard<std::mutex> lockClass(connectionManagerLock);

    connections.erase(connection);
}

void ConnectionManager::closeAll()
{
    std::lock_guard<std::mutex> lockClass(connectionManagerLock);

    for (const auto& connection : connections) {
        try {
            boost::system::error_code error;
            connection->socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
            connection->socket.close(error);
        } catch (boost::system::system_error&) {
        }
    }
    connections.clear();
}

// Connection

void Connection::close(bool force)
{
    //any thread
    ConnectionManager::getInstance().releaseConnection(shared_from_this());

    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);

    connectionState = CONNECTION_STATE_DISCONNECTED;

    if (protocol) {
        g_dispatcher.addTask(
            createTask(std::bind(&Protocol::release, protocol)));
    }

    if (messageQueue.empty() || force) {
        closeSocket();
    } else {
        //will be closed by the destructor or onWriteOperation
    }
}

void Connection::closeSocket()
{
    if (socket.is_open()) {
        try {
            readTimer.cancel();
            writeTimer.cancel();
            boost::system::error_code error;
            socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
            socket.close(error);
        } catch (boost::system::system_error& e) {
            std::cout << "[Network error - Connection::closeSocket] " << e.what() << std::endl;
        }
    }
}

Connection::~Connection()
{
    closeSocket();
}

void Connection::accept(Protocol_ptr protocol)
{
    this->protocol = protocol;
    g_dispatcher.addTask(createTask(std::bind(&Protocol::onConnect, protocol)));
    connectionState = CONNECTION_STATE_CONNECTING_STAGE2;

    accept();
}

void Connection::accept()
{
    if (connectionState == CONNECTION_STATE_PENDING) {
        connectionState = CONNECTION_STATE_CONNECTING_STAGE1;
    }
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    try {
        readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
        readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()), std::placeholders::_1));

        // Read size of the first packet
        boost::asio::async_read(socket,
                                boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH),
                                std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
    } catch (boost::system::system_error& e) {
        std::cout << "[Network error - Connection::accept] " << e.what() << std::endl;
        close(FORCE_CLOSE);
    }
}

void Connection::parseHeader(const boost::system::error_code& error)
{
    if (!receivedLastChar && connectionState == CONNECTION_STATE_CONNECTING_STAGE2) {
        uint8_t* msgBuffer = msg.getBuffer();
        std::string nullChar = "";
        std::string lastChar = "\n";

        if (!receivedName) {
            if (!(char)msgBuffer[1] == nullChar[0]) {
                receivedName = true;

                accept();
                return;
            } else {
                receivedLastChar = true;
            }
        } else {
            if ((char)msgBuffer[0] == lastChar[0] || (char)msgBuffer[1] == lastChar[0]) {
                receivedLastChar = true;
            }

            accept();
            return;
        }
    }

    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    readTimer.cancel();

    if (error) {
        close(FORCE_CLOSE);
        return;
    } else if (connectionState == CONNECTION_STATE_DISCONNECTED) {
        return;
    }

    if (receivedLastChar && connectionState == CONNECTION_STATE_CONNECTING_STAGE2) {
        connectionState = CONNECTION_STATE_GAME;
    }

    uint32_t timePassed = std::max<uint32_t>(1, (time(nullptr) - timeConnected) + 1);
    if ((++packetsSent / timePassed) > static_cast<uint32_t>(g_config.getNumber(ConfigManager::MAX_PACKETS_PER_SECOND))) {
        std::cout << convertIPToString(getIP()) << " disconnected for exceeding packet per second limit." << std::endl;
        close();
        return;
    }

    if (timePassed > 2) {
        timeConnected = time(nullptr);
        packetsSent = 0;
    }

    uint16_t size = msg.getLengthHeader();
    if (size == 0 || size >= NETWORKMESSAGE_MAXSIZE - 16) {
        std::cout << "Error" << std::endl;
        close(FORCE_CLOSE);
        return;
    }

    try {
        readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
        readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                           std::placeholders::_1));

        // Read packet content
        msg.setLength(size + NetworkMessage::HEADER_LENGTH);
        boost::asio::async_read(socket, boost::asio::buffer(msg.getBodyBuffer(), size),
                               std::bind(&Connection::parsePacket, shared_from_this(), std::placeholders::_1));
    } catch (boost::system::system_error& e) {
        std::cout << "[Network error - Connection::parseHeader] " << e.what() << std::endl;
        close(FORCE_CLOSE);
    }
}

void Connection::parsePacket(const boost::system::error_code& error)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    readTimer.cancel();

    if (error) {
        close(FORCE_CLOSE);
        return;
    } else if (connectionState == CONNECTION_STATE_DISCONNECTED) {
        return;
    }

    //Check packet checksum
    uint32_t checksum;
    int32_t len = msg.getLength() - msg.getBufferPosition() - NetworkMessage::CHECKSUM_LENGTH;
    if (len > 0) {
        checksum = adlerChecksum(msg.getBuffer() + msg.getBufferPosition() + NetworkMessage::CHECKSUM_LENGTH, len);
    } else {
        checksum = 0;
    }

    uint32_t recvChecksum = msg.get<uint32_t>();
    if (recvChecksum != checksum) {
        // it might not have been the checksum, step back
        msg.skipBytes(-NetworkMessage::CHECKSUM_LENGTH);
    }

    if (!receivedFirst) {
        // First message received
        receivedFirst = true;

        if (!protocol) {
            // Game protocol has already been created at this point
            protocol = service_port->make_protocol(recvChecksum == checksum, msg, shared_from_this());
            if (!protocol) {
                close(FORCE_CLOSE);
                return;
            }
        } else {
            msg.skipBytes(1);    // Skip protocol ID
        }

        protocol->onRecvFirstMessage(msg);
    } else {
        protocol->onRecvMessage(msg);    // Send the packet to the current protocol
    }

    try {
        readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
        readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                           std::placeholders::_1));

        // Wait to the next packet
        boost::asio::async_read(socket,
                               boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH),
                               std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
    } catch (boost::system::system_error& e) {
        std::cout << "[Network error - Connection::parsePacket] " << e.what() << std::endl;
        close(FORCE_CLOSE);
    }
}

void Connection::send(const OutputMessage_ptr& msg)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    if (connectionState == CONNECTION_STATE_DISCONNECTED) {
        return;
    }

    bool noPendingWrite = messageQueue.empty();
    messageQueue.emplace_back(msg);
    if (noPendingWrite) {
        internalSend(msg);
    }
}

void Connection::internalSend(const OutputMessage_ptr& msg)
{
    if (msg->isBroadcastMsg()) {
        dispatchBroadcastMessage(msg);
    }

    protocol->onSendMessage(msg);
    try {
        writeTimer.expires_from_now(boost::posix_time::seconds(Connection::write_timeout));
        writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                            std::placeholders::_1));

        boost::asio::async_write(socket,
                                boost::asio::buffer(msg->getOutputBuffer(), msg->getLength()),
                                std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1));
    } catch (boost::system::system_error& e) {
        std::cout << "[Network error - Connection::internalSend] " << e.what() << std::endl;
        close(FORCE_CLOSE);
    }
}

uint32_t Connection::getIP()
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);

    // IP-address is expressed in network byte order
    boost::system::error_code error;
    const boost::asio::ip::tcp::endpoint endpoint = socket.remote_endpoint(error);
    if (error) {
        return 0;
    }

    return htonl(endpoint.address().to_v4().to_ulong());
}

void Connection::dispatchBroadcastMessage(const OutputMessage_ptr& msg)
{
    auto msgCopy = OutputMessagePool::getOutputMessage();
    msgCopy->append(msg);
    socket.get_io_service().dispatch(std::bind(&Connection::broadcastMessage, shared_from_this(), msgCopy));
}

void Connection::broadcastMessage(OutputMessage_ptr msg)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    const auto client = std::dynamic_pointer_cast<ProtocolGame>(protocol);
    if (client) {
        std::lock_guard<decltype(client->liveCastLock)> lockGuard(client->liveCastLock);

        const auto& spectators = client->getLiveCastSpectators();
        for (const ProtocolSpectator_ptr& spectator : spectators) {
            auto newMsg = OutputMessagePool::getOutputMessage();
            newMsg->append(msg);
            spectator->send(std::move(newMsg));
        }
    }
}

void Connection::onWriteOperation(const boost::system::error_code& error)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    writeTimer.cancel();
    messageQueue.pop_front();

    if (error) {
        messageQueue.clear();
        close(FORCE_CLOSE);
        return;
    }

    if (!messageQueue.empty()) {
        internalSend(messageQueue.front());
    } else if (connectionState == CONNECTION_STATE_DISCONNECTED) {
        closeSocket();
    }
}

void Connection::handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error)
{
    if (error == boost::asio::error::operation_aborted) {
        //The timer has been manually cancelled
        return;
    }

    if (auto connection = connectionWeak.lock()) {
        connection->close(FORCE_CLOSE);
    }
}

.h
C++:
/**
 * The Forgotten Server - a free and open-source MMORPG server emulator
 * Copyright (C) 2017  Mark Samman <[email protected]>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#ifndef FS_CONNECTION_H_FC8E1B4392D24D27A2F129D8B93A6348
#define FS_CONNECTION_H_FC8E1B4392D24D27A2F129D8B93A6348

#include <unordered_set>

#include "networkmessage.h"

class Protocol;
using Protocol_ptr = std::shared_ptr<Protocol>;
class OutputMessage;
using OutputMessage_ptr = std::shared_ptr<OutputMessage>;
class Connection;
using Connection_ptr = std::shared_ptr<Connection>;
using ConnectionWeak_ptr = std::weak_ptr<Connection>;
class ServiceBase;
using Service_ptr = std::shared_ptr<ServiceBase>;
class ServicePort;
using ServicePort_ptr = std::shared_ptr<ServicePort>;
using ConstServicePort_ptr = std::shared_ptr<const ServicePort>;

class ConnectionManager
{
    public:
        static ConnectionManager& getInstance() {
            static ConnectionManager instance;
            return instance;
        }

        Connection_ptr createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort);
        void releaseConnection(const Connection_ptr& connection);
        void closeAll();

    protected:
        ConnectionManager() = default;

        std::unordered_set<Connection_ptr> connections;
        std::mutex connectionManagerLock;
};

class Connection : public std::enable_shared_from_this<Connection>
{
    public:
        // non-copyable
        Connection(const Connection&) = delete;
        Connection& operator=(const Connection&) = delete;

        enum { write_timeout = 30 };
        enum { read_timeout = 30 };

        enum ConnectionState_t : int8_t {
            CONNECTION_STATE_DISCONNECTED,
            CONNECTION_STATE_CONNECTING_STAGE1,
            CONNECTION_STATE_CONNECTING_STAGE2,
            CONNECTION_STATE_GAME,
            CONNECTION_STATE_PENDING
        };

        enum { FORCE_CLOSE = true };

        Connection(boost::asio::io_service& io_service,
                  ConstServicePort_ptr service_port) :
            readTimer(io_service),
            writeTimer(io_service),
            service_port(std::move(service_port)),
            socket(io_service) {
            connectionState = CONNECTION_STATE_PENDING;
            receivedFirst = false;
            receivedName = false;
            receivedLastChar = false;
            packetsSent = 0;
            timeConnected = time(nullptr);
        }
        ~Connection();

        friend class ConnectionManager;

        void close(bool force = false);
        // Used by protocols that require server to send first
        void accept(Protocol_ptr protocol);
        void accept();

        void send(const OutputMessage_ptr& msg);

        uint32_t getIP();

    private:
        void parseHeader(const boost::system::error_code& error);
        void parsePacket(const boost::system::error_code& error);

        void onWriteOperation(const boost::system::error_code& error);

        static void handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error);

        void closeSocket();
        void internalSend(const OutputMessage_ptr& msg);

        boost::asio::ip::tcp::socket& getSocket() {
            return socket;
        }
        friend class ServicePort;

        NetworkMessage msg;
        void broadcastMessage(OutputMessage_ptr msg);
        void dispatchBroadcastMessage(const OutputMessage_ptr& msg);

        boost::asio::deadline_timer readTimer;
        boost::asio::deadline_timer writeTimer;

        std::recursive_mutex connectionLock;

        std::list<OutputMessage_ptr> messageQueue;

        ConstServicePort_ptr service_port;
        Protocol_ptr protocol;

        boost::asio::ip::tcp::socket socket;

        time_t timeConnected;
        uint32_t packetsSent;

        int8_t connectionState;
        bool receivedFirst;

        bool receivedName;
        bool receivedLastChar;
};

#endif

But just as the Milice code does not work when there are many players.
@Jo3Bingham, is there any way to give skip UTF byte of the server name if it gets receivedName = true;
I've tried it to many times, but if it does not update without execute accept(); It only receives the first 2 characters. The Milice code does the same thing (I have the function onRecvServerMessage)
 
Last edited by a moderator:
My code seems to be working fine. However, you need to edit the first bytes to match your server name. Also add bool m_receivingWorldName to connection.h
I use TFS 1.0
C++:
void Connection::parseHeader(const boost::system::error_code& error)
{
    m_connectionLock.lock();
    m_readTimer.cancel();

    if (!m_receivedFirst) {
        m_msg.setReadPos(0);
        uint8_t first = m_msg.GetByte();
        uint8_t second = 0x00;

        if (!m_receivingWorldName) {
            second = m_msg.GetByte();
        }
        m_msg.setReadPos(0);
        if (first == 0x47 && second == 0x75) {
            m_receivingWorldName = true;
        }


        if (m_receivingWorldName) {
            if (first == 0x0A) {
                m_receivingWorldName = false;
                try {
                    m_readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
                    m_readTimer.async_wait(
                            std::bind(&Connection::handleReadTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                      std::placeholders::_1));

                    // Wait to the next packet
                    boost::asio::async_read(getHandle(),
                                            boost::asio::buffer(m_msg.getBuffer(), NetworkMessage::header_length),
                                            std::bind(&Connection::parseHeader, shared_from_this(),
                                                      std::placeholders::_1));
                } catch (boost::system::system_error& e) {
                    if (m_logError) {
                        std::cout << "[Network error - Connection::parseHeader] " << e.what() << std::endl;
                        m_logError = false;
                    }
                    closeConnection();
                }
            } else {
                try {
                    m_readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
                    m_readTimer.async_wait(
                            std::bind(&Connection::handleReadTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                      std::placeholders::_1));

                    // Wait to the next packet
                    boost::asio::async_read(getHandle(),
                                            boost::asio::buffer(m_msg.getBuffer(), 1),
                                            std::bind(&Connection::parseHeader, shared_from_this(),
                                                      std::placeholders::_1));
                } catch (boost::system::system_error& e) {
                    if (m_logError) {
                        std::cout << "[Network error - Connection::parseHeader] " << e.what() << std::endl;
                        m_logError = false;
                    }
                    closeConnection();
                }
            }
            m_connectionLock.unlock();
            return;
        }
    }

    int32_t size = m_msg.decodeHeader();
    if (error || size <= 0 || size >= NETWORKMESSAGE_MAXSIZE - 16) {
        handleReadError(error);
    }

    if (m_connectionState != CONNECTION_STATE_OPEN || m_readError) {
        closeConnection();
        m_connectionLock.unlock();
        return;
    }

    uint32_t timePassed = std::max<uint32_t>(1, (time(nullptr) - m_timeConnected) + 1);
    if ((++m_packetsSent / timePassed) > (uint32_t)g_config.getNumber(ConfigManager::MAX_PACKETS_PER_SECOND)) {
        std::cout << convertIPToString(getIP()) << " disconnected for exceeding packet per second limit." << std::endl;
        closeConnection();
        m_connectionLock.unlock();
        return;
    }

    if (timePassed > 2) {
        m_timeConnected = time(nullptr);
        m_packetsSent = 0;
    }

    try {
        m_readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
        m_readTimer.async_wait( std::bind(&Connection::handleReadTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                            std::placeholders::_1));

        // Read packet content
        m_msg.setMessageLength(size + NetworkMessage::header_length);
        boost::asio::async_read(getHandle(), boost::asio::buffer(m_msg.getBodyBuffer(), size),
                                std::bind(&Connection::parsePacket, shared_from_this(), std::placeholders::_1));
    } catch (boost::system::system_error& e) {
        if (m_logError) {
            std::cout << "[Network error - Connection::parseHeader] " << e.what() << std::endl;
            m_logError = false;
        }

        closeConnection();
    }

    m_connectionLock.unlock();
}
 
Last edited by a moderator:
@gunz You are a genius! If the server name no has odd characters, the next boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH) bug. Need to send byte per byte!

My new connection.cpp (updated 02/09/17 - MM/DD/YY)
C++:
/**
 * The Forgotten Server - a free and open-source MMORPG server emulator
 * Copyright (C) 2017  Mark Samman <[email protected]>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "otpch.h"

#include "configmanager.h"
#include "connection.h"
#include "outputmessage.h"
#include "protocol.h"
#include "protocolgame.h"
#include "scheduler.h"
#include "server.h"

extern ConfigManager g_config;

Connection_ptr ConnectionManager::createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort)
{
    std::lock_guard<std::mutex> lockClass(connectionManagerLock);

    auto connection = std::make_shared<Connection>(io_service, servicePort);
    connections.insert(connection);
    return connection;
}

void ConnectionManager::releaseConnection(const Connection_ptr& connection)
{
    std::lock_guard<std::mutex> lockClass(connectionManagerLock);

    connections.erase(connection);
}

void ConnectionManager::closeAll()
{
    std::lock_guard<std::mutex> lockClass(connectionManagerLock);

    for (const auto& connection : connections) {
        try {
            boost::system::error_code error;
            connection->socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
            connection->socket.close(error);
        } catch (boost::system::system_error&) {
        }
    }
    connections.clear();
}

// Connection

void Connection::close(bool force)
{
    //any thread
    ConnectionManager::getInstance().releaseConnection(shared_from_this());

    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);

    connectionState = CONNECTION_STATE_DISCONNECTED;

    if (protocol) {
        g_dispatcher.addTask(
            createTask(std::bind(&Protocol::release, protocol)));
    }

    if (messageQueue.empty() || force) {
        closeSocket();
    } else {
        //will be closed by the destructor or onWriteOperation
    }
}

void Connection::closeSocket()
{
    if (socket.is_open()) {
        try {
            readTimer.cancel();
            writeTimer.cancel();
            boost::system::error_code error;
            socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
            socket.close(error);
        } catch (boost::system::system_error& e) {
            std::cout << "[Network error - Connection::closeSocket] " << e.what() << std::endl;
        }
    }
}

Connection::~Connection()
{
    closeSocket();
}

void Connection::accept(Protocol_ptr protocol)
{
    this->protocol = protocol;
    g_dispatcher.addTask(createTask(std::bind(&Protocol::onConnect, protocol)));
    connectionState = CONNECTION_STATE_CONNECTING_STAGE2;

    accept();
}

void Connection::accept()
{
    if (connectionState == CONNECTION_STATE_PENDING) {
        connectionState = CONNECTION_STATE_CONNECTING_STAGE1;
    }
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    try {
        readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
        readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()), std::placeholders::_1));

        if (!receivedLastChar && receivedName && connectionState == CONNECTION_STATE_CONNECTING_STAGE2) {
            // Read size of the first packet
            boost::asio::async_read(socket,
                                boost::asio::buffer(msg.getBuffer(), 1),
                                std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
        } else {
            // Read size of the first packet
            boost::asio::async_read(socket,
                                    boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH),
                                    std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
        }
    } catch (boost::system::system_error& e) {
        std::cout << "[Network error - Connection::accept] " << e.what() << std::endl;
        close(FORCE_CLOSE);
    }
}

void Connection::parseHeader(const boost::system::error_code& error)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    readTimer.cancel();

    if (error) {
        close(FORCE_CLOSE);
        return;
    } else if (connectionState == CONNECTION_STATE_DISCONNECTED) {
        return;
    }

    uint32_t timePassed = std::max<uint32_t>(1, (time(nullptr) - timeConnected) + 1);
    if ((++packetsSent / timePassed) > static_cast<uint32_t>(g_config.getNumber(ConfigManager::MAX_PACKETS_PER_SECOND))) {
        std::cout << convertIPToString(getIP()) << " disconnected for exceeding packet per second limit." << std::endl;
        close();
        return;
    }

    if (!receivedLastChar && connectionState == CONNECTION_STATE_CONNECTING_STAGE2) {
        uint8_t* msgBuffer = msg.getBuffer();

        if (!receivedName && msgBuffer[1] == 0x00) {
            receivedLastChar = true;
        } else {
            std::string serverName = g_config.getString(ConfigManager::SERVER_NAME) + "\n";

            if (!receivedName) {
                if ((char)msgBuffer[0] == serverName[0] && (char)msgBuffer[1] == serverName[1]) {
                    receivedName = true;
                    serverNameTime = 1;

                    accept();
                    return;
                } else {
                    std::cout << "[Network error - Connection::parseHeader] Invalid Client Login" << std::endl;
                    close(FORCE_CLOSE);
                    return;
                }
            }
            ++serverNameTime;

            if ((char)msgBuffer[0] == serverName[serverNameTime]) {
                if (msgBuffer[0] == 0x0A) {
                    receivedLastChar = true;
                }

                accept();
                return;
            } else {
                std::cout << "[Network error - Connection::parseHeader] Invalid Client Login" << std::endl;
                close(FORCE_CLOSE);
                return;
            }
        }
    }

    if (receivedLastChar && connectionState == CONNECTION_STATE_CONNECTING_STAGE2) {
        connectionState = CONNECTION_STATE_GAME;
    }

    if (timePassed > 2) {
        timeConnected = time(nullptr);
        packetsSent = 0;
    }

    uint16_t size = msg.getLengthHeader();
    if (size == 0 || size >= NETWORKMESSAGE_MAXSIZE - 16) {
        close(FORCE_CLOSE);
        return;
    }

    try {
        readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
        readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                           std::placeholders::_1));

        // Read packet content
        msg.setLength(size + NetworkMessage::HEADER_LENGTH);
        boost::asio::async_read(socket, boost::asio::buffer(msg.getBodyBuffer(), size),
                               std::bind(&Connection::parsePacket, shared_from_this(), std::placeholders::_1));
    } catch (boost::system::system_error& e) {
        std::cout << "[Network error - Connection::parseHeader] " << e.what() << std::endl;
        close(FORCE_CLOSE);
    }
}

void Connection::parsePacket(const boost::system::error_code& error)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    readTimer.cancel();

    if (error) {
        close(FORCE_CLOSE);
        return;
    } else if (connectionState == CONNECTION_STATE_DISCONNECTED) {
        return;
    }

    //Check packet checksum
    uint32_t checksum;
    int32_t len = msg.getLength() - msg.getBufferPosition() - NetworkMessage::CHECKSUM_LENGTH;
    if (len > 0) {
        checksum = adlerChecksum(msg.getBuffer() + msg.getBufferPosition() + NetworkMessage::CHECKSUM_LENGTH, len);
    } else {
        checksum = 0;
    }

    uint32_t recvChecksum = msg.get<uint32_t>();
    if (recvChecksum != checksum) {
        // it might not have been the checksum, step back
        msg.skipBytes(-NetworkMessage::CHECKSUM_LENGTH);
    }

    if (!receivedFirst) {
        // First message received
        receivedFirst = true;

        if (!protocol) {
            // Game protocol has already been created at this point
            protocol = service_port->make_protocol(recvChecksum == checksum, msg, shared_from_this());
            if (!protocol) {
                close(FORCE_CLOSE);
                return;
            }
        } else {
            msg.skipBytes(1);    // Skip protocol ID
        }

        protocol->onRecvFirstMessage(msg);
    } else {
        protocol->onRecvMessage(msg);    // Send the packet to the current protocol
    }

    try {
        readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
        readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                           std::placeholders::_1));

        // Wait to the next packet
        boost::asio::async_read(socket,
                               boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH),
                               std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
    } catch (boost::system::system_error& e) {
        std::cout << "[Network error - Connection::parsePacket] " << e.what() << std::endl;
        close(FORCE_CLOSE);
    }
}

void Connection::send(const OutputMessage_ptr& msg)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    if (connectionState == CONNECTION_STATE_DISCONNECTED) {
        return;
    }

    bool noPendingWrite = messageQueue.empty();
    messageQueue.emplace_back(msg);
    if (noPendingWrite) {
        internalSend(msg);
    }
}

void Connection::internalSend(const OutputMessage_ptr& msg)
{
    if (msg->isBroadcastMsg()) {
        dispatchBroadcastMessage(msg);
    }

    protocol->onSendMessage(msg);
    try {
        writeTimer.expires_from_now(boost::posix_time::seconds(Connection::write_timeout));
        writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                            std::placeholders::_1));

        boost::asio::async_write(socket,
                                boost::asio::buffer(msg->getOutputBuffer(), msg->getLength()),
                                std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1));
    } catch (boost::system::system_error& e) {
        std::cout << "[Network error - Connection::internalSend] " << e.what() << std::endl;
        close(FORCE_CLOSE);
    }
}

uint32_t Connection::getIP()
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);

    // IP-address is expressed in network byte order
    boost::system::error_code error;
    const boost::asio::ip::tcp::endpoint endpoint = socket.remote_endpoint(error);
    if (error) {
        return 0;
    }

    return htonl(endpoint.address().to_v4().to_ulong());
}

void Connection::dispatchBroadcastMessage(const OutputMessage_ptr& msg)
{
    auto msgCopy = OutputMessagePool::getOutputMessage();
    msgCopy->append(msg);
    socket.get_io_service().dispatch(std::bind(&Connection::broadcastMessage, shared_from_this(), msgCopy));
}

void Connection::broadcastMessage(OutputMessage_ptr msg)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    const auto client = std::dynamic_pointer_cast<ProtocolGame>(protocol);
    if (client) {
        std::lock_guard<decltype(client->liveCastLock)> lockGuard(client->liveCastLock);

        const auto& spectators = client->getLiveCastSpectators();
        for (const ProtocolSpectator_ptr& spectator : spectators) {
            auto newMsg = OutputMessagePool::getOutputMessage();
            newMsg->append(msg);
            spectator->send(std::move(newMsg));
        }
    }
}

void Connection::onWriteOperation(const boost::system::error_code& error)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    writeTimer.cancel();
    messageQueue.pop_front();

    if (error) {
        messageQueue.clear();
        close(FORCE_CLOSE);
        return;
    }

    if (!messageQueue.empty()) {
        internalSend(messageQueue.front());
    } else if (connectionState == CONNECTION_STATE_DISCONNECTED) {
        closeSocket();
    }
}

void Connection::handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error)
{
    if (error == boost::asio::error::operation_aborted) {
        //The timer has been manually cancelled
        return;
    }

    if (auto connection = connectionWeak.lock()) {
        connection->close(FORCE_CLOSE);
    }
}

@Milice code revision:
C++:
void Connection::parseHeader(const boost::system::error_code& error)
{
    if(!receivedServerName) {
        uint8_t* msgBuffer = msg.getBuffer();
        std::string serverName = g_config.getString(ConfigManager::SERVER_NAME);

        if(receivedServerNameFirst == false && !(((char)msgBuffer[0] == serverName[0]) && ((char)msgBuffer[1] == serverName[1]))) {
            receivedServerName = true;
            receivedServerNameFirst = true;
        } else {
            if(!receivedServerNameFirst) {
                receivedServerNameFirst = true;
            }

            try {
                readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
                readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                        std::placeholders::_1));

                if (msgBuffer[0] == 0x0A) {
                    receivedServerName = true;
                    // Wait to the next packet
                    boost::asio::async_read(socket,
                                            boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH),
                                            std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
                } else {
                    // Wait to the next packet
                    boost::asio::async_read(socket,
                                            boost::asio::buffer(msg.getBuffer(), 1,
                                            std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
                }
            } catch (boost::system::system_error& e) {
                std::cout << "[Network error - Connection::parsePacket] " << e.what() << std::endl;
                close(FORCE_CLOSE);
            }
            return;
        }
    }
 
Last edited:
I have alots of errors while compile with the changes, could tell me all the declarations of the variables ?
 
sure, I'll wait for this awesome code :D
Try this

.cpp
Code:
/**
 * The Forgotten Server - a free and open-source MMORPG server emulator
 * Copyright (C) 2017  Mark Samman <[email protected]>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "otpch.h"

#include "configmanager.h"
#include "connection.h"
#include "outputmessage.h"
#include "protocol.h"
#include "protocolgame.h"
#include "scheduler.h"
#include "server.h"

extern ConfigManager g_config;

Connection_ptr ConnectionManager::createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort)
{
    std::lock_guard<std::mutex> lockClass(connectionManagerLock);

    auto connection = std::make_shared<Connection>(io_service, servicePort);
    connections.insert(connection);
    return connection;
}

void ConnectionManager::releaseConnection(const Connection_ptr& connection)
{
    std::lock_guard<std::mutex> lockClass(connectionManagerLock);

    connections.erase(connection);
}

void ConnectionManager::closeAll()
{
    std::lock_guard<std::mutex> lockClass(connectionManagerLock);

    for (const auto& connection : connections) {
        try {
            boost::system::error_code error;
            connection->socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
            connection->socket.close(error);
        } catch (boost::system::system_error&) {
        }
    }
    connections.clear();
}

// Connection

void Connection::close(bool force)
{
    //any thread
    ConnectionManager::getInstance().releaseConnection(shared_from_this());

    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);

    connectionState = CONNECTION_STATE_DISCONNECTED;

    if (protocol) {
        g_dispatcher.addTask(
            createTask(std::bind(&Protocol::release, protocol)));
    }

    if (messageQueue.empty() || force) {
        closeSocket();
    } else {
        //will be closed by the destructor or onWriteOperation
    }
}

void Connection::closeSocket()
{
    if (socket.is_open()) {
        try {
            readTimer.cancel();
            writeTimer.cancel();
            boost::system::error_code error;
            socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, error);
            socket.close(error);
        } catch (boost::system::system_error& e) {
            std::cout << "[Network error - Connection::closeSocket] " << e.what() << std::endl;
        }
    }
}

Connection::~Connection()
{
    closeSocket();
}

void Connection::accept(Protocol_ptr protocol)
{
    this->protocol = protocol;
    g_dispatcher.addTask(createTask(std::bind(&Protocol::onConnect, protocol)));
    connectionState = CONNECTION_STATE_CONNECTING_STAGE2;

    accept();
}

void Connection::accept()
{
    if (connectionState == CONNECTION_STATE_PENDING) {
        connectionState = CONNECTION_STATE_CONNECTING_STAGE1;
    }
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    try {
        readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
        readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()), std::placeholders::_1));

        if (!receivedLastChar && receivedName && connectionState == CONNECTION_STATE_CONNECTING_STAGE2) {
            // Read size of the first packet
            boost::asio::async_read(socket,
                                boost::asio::buffer(msg.getBuffer(), 1),
                                std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
        } else {
            // Read size of the first packet
            boost::asio::async_read(socket,
                                    boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH),
                                    std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
        }
    } catch (boost::system::system_error& e) {
        std::cout << "[Network error - Connection::accept] " << e.what() << std::endl;
        close(FORCE_CLOSE);
    }
}

void Connection::parseHeader(const boost::system::error_code& error)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    readTimer.cancel();

    if (error) {
        close(FORCE_CLOSE);
        return;
    } else if (connectionState == CONNECTION_STATE_DISCONNECTED) {
        return;
    }

    uint32_t timePassed = std::max<uint32_t>(1, (time(nullptr) - timeConnected) + 1);
    if ((++packetsSent / timePassed) > static_cast<uint32_t>(g_config.getNumber(ConfigManager::MAX_PACKETS_PER_SECOND))) {
        std::cout << convertIPToString(getIP()) << " disconnected for exceeding packet per second limit." << std::endl;
        close();
        return;
    }

    if (!receivedLastChar && connectionState == CONNECTION_STATE_CONNECTING_STAGE2) {
        uint8_t* msgBuffer = msg.getBuffer();

        if (!receivedName && msgBuffer[1] == 0x00) {
            receivedLastChar = true;
        } else {
            std::string serverName = g_config.getString(ConfigManager::SERVER_NAME) + "\n";

            if (!receivedName) {
                if ((char)msgBuffer[0] == serverName[0] && (char)msgBuffer[1] == serverName[1]) {
                    receivedName = true;
                    serverNameTime = 1;

                    accept();
                    return;
                } else {
                    std::cout << "[Network error - Connection::parseHeader] Invalid Client Login" << std::endl;
                    close(FORCE_CLOSE);
                    return;
                }
            }
            ++serverNameTime;

            if ((char)msgBuffer[0] == serverName[serverNameTime]) {
                if (msgBuffer[0] == 0x0A) {
                    receivedLastChar = true;
                }

                accept();
                return;
            } else {
                std::cout << "[Network error - Connection::parseHeader] Invalid Client Login" << std::endl;
                close(FORCE_CLOSE);
                return;
            }
        }
    }

    if (receivedLastChar && connectionState == CONNECTION_STATE_CONNECTING_STAGE2) {
        connectionState = CONNECTION_STATE_GAME;
    }

    if (timePassed > 2) {
        timeConnected = time(nullptr);
        packetsSent = 0;
    }

    uint16_t size = msg.getLengthHeader();
    if (size == 0 || size >= NETWORKMESSAGE_MAXSIZE - 16) {
        close(FORCE_CLOSE);
        return;
    }

    try {
        readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
        readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                           std::placeholders::_1));

        // Read packet content
        msg.setLength(size + NetworkMessage::HEADER_LENGTH);
        boost::asio::async_read(socket, boost::asio::buffer(msg.getBodyBuffer(), size),
                               std::bind(&Connection::parsePacket, shared_from_this(), std::placeholders::_1));
    } catch (boost::system::system_error& e) {
        std::cout << "[Network error - Connection::parseHeader] " << e.what() << std::endl;
        close(FORCE_CLOSE);
    }
}

void Connection::parsePacket(const boost::system::error_code& error)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    readTimer.cancel();

    if (error) {
        close(FORCE_CLOSE);
        return;
    } else if (connectionState == CONNECTION_STATE_DISCONNECTED) {
        return;
    }

    //Check packet checksum
    uint32_t checksum;
    int32_t len = msg.getLength() - msg.getBufferPosition() - NetworkMessage::CHECKSUM_LENGTH;
    if (len > 0) {
        checksum = adlerChecksum(msg.getBuffer() + msg.getBufferPosition() + NetworkMessage::CHECKSUM_LENGTH, len);
    } else {
        checksum = 0;
    }

    uint32_t recvChecksum = msg.get<uint32_t>();
    if (recvChecksum != checksum) {
        // it might not have been the checksum, step back
        msg.skipBytes(-NetworkMessage::CHECKSUM_LENGTH);
    }

    if (!receivedFirst) {
        // First message received
        receivedFirst = true;

        if (!protocol) {
            // Game protocol has already been created at this point
            protocol = service_port->make_protocol(recvChecksum == checksum, msg, shared_from_this());
            if (!protocol) {
                close(FORCE_CLOSE);
                return;
            }
        } else {
            msg.skipBytes(1);    // Skip protocol ID
        }

        protocol->onRecvFirstMessage(msg);
    } else {
        protocol->onRecvMessage(msg);    // Send the packet to the current protocol
    }

    try {
        readTimer.expires_from_now(boost::posix_time::seconds(Connection::read_timeout));
        readTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                           std::placeholders::_1));

        // Wait to the next packet
        boost::asio::async_read(socket,
                               boost::asio::buffer(msg.getBuffer(), NetworkMessage::HEADER_LENGTH),
                               std::bind(&Connection::parseHeader, shared_from_this(), std::placeholders::_1));
    } catch (boost::system::system_error& e) {
        std::cout << "[Network error - Connection::parsePacket] " << e.what() << std::endl;
        close(FORCE_CLOSE);
    }
}

void Connection::send(const OutputMessage_ptr& msg)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    if (connectionState == CONNECTION_STATE_DISCONNECTED) {
        return;
    }

    bool noPendingWrite = messageQueue.empty();
    messageQueue.emplace_back(msg);
    if (noPendingWrite) {
        internalSend(msg);
    }
}

void Connection::internalSend(const OutputMessage_ptr& msg)
{
    if (msg->isBroadcastMsg()) {
        dispatchBroadcastMessage(msg);
    }

    protocol->onSendMessage(msg);
    try {
        writeTimer.expires_from_now(boost::posix_time::seconds(Connection::write_timeout));
        writeTimer.async_wait(std::bind(&Connection::handleTimeout, std::weak_ptr<Connection>(shared_from_this()),
                                            std::placeholders::_1));

        boost::asio::async_write(socket,
                                boost::asio::buffer(msg->getOutputBuffer(), msg->getLength()),
                                std::bind(&Connection::onWriteOperation, shared_from_this(), std::placeholders::_1));
    } catch (boost::system::system_error& e) {
        std::cout << "[Network error - Connection::internalSend] " << e.what() << std::endl;
        close(FORCE_CLOSE);
    }
}

uint32_t Connection::getIP()
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);

    // IP-address is expressed in network byte order
    boost::system::error_code error;
    const boost::asio::ip::tcp::endpoint endpoint = socket.remote_endpoint(error);
    if (error) {
        return 0;
    }

    return htonl(endpoint.address().to_v4().to_ulong());
}

void Connection::dispatchBroadcastMessage(const OutputMessage_ptr& msg)
{
    auto msgCopy = OutputMessagePool::getOutputMessage();
    msgCopy->append(msg);
    socket.get_io_service().dispatch(std::bind(&Connection::broadcastMessage, shared_from_this(), msgCopy));
}

void Connection::broadcastMessage(OutputMessage_ptr msg)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    const auto client = std::dynamic_pointer_cast<ProtocolGame>(protocol);
    if (client) {
        std::lock_guard<decltype(client->liveCastLock)> lockGuard(client->liveCastLock);

        const auto& spectators = client->getLiveCastSpectators();
        for (const ProtocolSpectator_ptr& spectator : spectators) {
            auto newMsg = OutputMessagePool::getOutputMessage();
            newMsg->append(msg);
            spectator->send(std::move(newMsg));
        }
    }
}

void Connection::onWriteOperation(const boost::system::error_code& error)
{
    std::lock_guard<std::recursive_mutex> lockClass(connectionLock);
    writeTimer.cancel();
    messageQueue.pop_front();

    if (error) {
        messageQueue.clear();
        close(FORCE_CLOSE);
        return;
    }

    if (!messageQueue.empty()) {
        internalSend(messageQueue.front());
    } else if (connectionState == CONNECTION_STATE_DISCONNECTED) {
        closeSocket();
    }
}

void Connection::handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error)
{
    if (error == boost::asio::error::operation_aborted) {
        //The timer has been manually cancelled
        return;
    }

    if (auto connection = connectionWeak.lock()) {
        connection->close(FORCE_CLOSE);
    }
}

.h
Code:
/**
* The Forgotten Server - a free and open-source MMORPG server emulator
* Copyright (C) 2017  Mark Samman <[email protected]>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/

#ifndef FS_CONNECTION_H_FC8E1B4392D24D27A2F129D8B93A6348
#define FS_CONNECTION_H_FC8E1B4392D24D27A2F129D8B93A6348

#include <unordered_set>

#include "networkmessage.h"

class Protocol;
using Protocol_ptr = std::shared_ptr<Protocol>;
class OutputMessage;
using OutputMessage_ptr = std::shared_ptr<OutputMessage>;
class Connection;
using Connection_ptr = std::shared_ptr<Connection> ;
using ConnectionWeak_ptr = std::weak_ptr<Connection>;
class ServiceBase;
using Service_ptr = std::shared_ptr<ServiceBase>;
class ServicePort;
using ServicePort_ptr = std::shared_ptr<ServicePort>;
using ConstServicePort_ptr = std::shared_ptr<const ServicePort>;

class ConnectionManager
{
    public:
        static ConnectionManager& getInstance() {
            static ConnectionManager instance;
            return instance;
        }

        Connection_ptr createConnection(boost::asio::io_service& io_service, ConstServicePort_ptr servicePort);
        void releaseConnection(const Connection_ptr& connection);
        void closeAll();

    protected:
        ConnectionManager() = default;

        std::unordered_set<Connection_ptr> connections;
        std::mutex connectionManagerLock;
};

class Connection : public std::enable_shared_from_this<Connection>
{
    public:
        // non-copyable
        Connection(const Connection&) = delete;
        Connection& operator=(const Connection&) = delete;

        enum { write_timeout = 30 };
        enum { read_timeout = 30 };

        enum ConnectionState_t : int8_t {
            CONNECTION_STATE_DISCONNECTED,
            CONNECTION_STATE_CONNECTING_STAGE1,
            CONNECTION_STATE_CONNECTING_STAGE2,
            CONNECTION_STATE_GAME,
            CONNECTION_STATE_PENDING
        };

        enum { FORCE_CLOSE = true };

        Connection(boost::asio::io_service& io_service,
                   ConstServicePort_ptr service_port) :
            readTimer(io_service),
            writeTimer(io_service),
            service_port(std::move(service_port)),
            socket(io_service) {
            connectionState = CONNECTION_STATE_PENDING;
            packetsSent = 0;
            timeConnected = time(nullptr);
            receivedFirst = false;
            serverNameTime = 0;
            receivedName = false;
            receivedLastChar = false;
        }
        ~Connection();

        friend class ConnectionManager;

        void close(bool force = false);
        // Used by protocols that require server to send first
        void accept(Protocol_ptr protocol);
        void accept();

        void send(const OutputMessage_ptr& msg);

        uint32_t getIP();

    private:
        void parseHeader(const boost::system::error_code& error);
        void parsePacket(const boost::system::error_code& error);

        void onWriteOperation(const boost::system::error_code& error);

        static void handleTimeout(ConnectionWeak_ptr connectionWeak, const boost::system::error_code& error);

        void closeSocket();
        void internalSend(const OutputMessage_ptr& msg);

        boost::asio::ip::tcp::socket& getSocket() {
            return socket;
        }
        friend class ServicePort;

        NetworkMessage msg;
                                           
                                                           

        boost::asio::deadline_timer readTimer;
        boost::asio::deadline_timer writeTimer;

        std::recursive_mutex connectionLock;

        std::list<OutputMessage_ptr> messageQueue;

        ConstServicePort_ptr service_port;
        Protocol_ptr protocol;

        boost::asio::ip::tcp::socket socket;

        time_t timeConnected;
        uint32_t packetsSent;

        int8_t connectionState;
        bool receivedFirst;

        uint32_t serverNameTime;
        bool receivedName;
        bool receivedLastChar;
};

#endif
 
Last edited:
Code:
1>..\src\connection.cpp(71): error C2065: 'CONNECTION_STATE_OPEN' : undeclared identifier
1>..\src\connection.cpp(74): error C2065: 'CONNECTION_STATE_CLOSED' : undeclared identifier
1>..\src\connection.cpp(217): error C2039: 'getLengthHeader' : is not a member of 'NetworkMessage'
1> c:\forgottenserver\src\networkmessage.h(32) : see declaration of 'NetworkMessage'
 
Code:
1>..\src\connection.cpp(71): error C2065: 'CONNECTION_STATE_OPEN' : undeclared identifier
1>..\src\connection.cpp(74): error C2065: 'CONNECTION_STATE_CLOSED' : undeclared identifier
1>..\src\connection.cpp(217): error C2039: 'getLengthHeader' : is not a member of 'NetworkMessage'
1> c:\forgottenserver\src\networkmessage.h(32) : see declaration of 'NetworkMessage'
Updated, the getLengthHeader is from tfs 1.3, no my error...
https://github.com/otland/forgottenserver/commit/5bfac5e366032342a87b51ec2ba5d262cf1a97a9
 
well, I resolve the errors, but now in the client have "ilegal value" also, when I enter in the client 10.99 when log the character the character disappear and logout, but still the game window, as when happen connection lost
6Wfmhe2.jpg

edit: fix just a comment line in the login.php but now get an error in the console while try enter in the game "[Network error - Connection::parseHeader] Invalid Client Login"
 
Last edited:
Back
Top