URL: https://www.github.com/ethanmoffat/etheos

Background

eoserv is a popular open-source option for running an emulated game server for Endless Online. However, the developer experience for setting up a local environment is extremely lacking. There are also a number of features in the game that remain unimplemented in vanilla eoserv.

Project Goals

There are two primary goals for etheos:

  1. Provide an excellent developer experience that simplifies setting up a local development environment
  2. Implement missing features from vanilla eoserv

Features

SQL Server Support

Vanilla EOSERV ships with database support for MySQL (MariaDB) and Sqlite. Microsoft SQL Server support was added using the ODBC driver libraries as an alternate database engine.

Testing is usually done with SQL Server running in a local docker container.

bcrypt password hashing and threadpool-queued logins

Vanilla EOSERV ships with sha256 as the password hashing function. While better than nothing at all, sha256 is a bit outdated as a password hashing solution and doesn’t provide as strong of security as something like bcrypt. bcrypt support was added for all password hashing operations which significantly increases the security of stored account data.

Since bcrypt password hashes can take a long time, and EOSERV is architected as a single-threaded application, the entire game server can hang each time a login, account create, or change password operation is initiated. To avoid this, a thread pooling system was implemented to queue work for password operations on background threads. This allows for login/account create requests to be processed asynchronously.

Databases generally require operations to be performed on the same thread. To support background thread operations, database connections are opened on a per-thread basis.

An example of the code calling a password operation:

// Username is captured by value so the function can safely return without the memory being deallocated / stack corrupted.
std::string username(accountInfo.username);

auto successCallback = [username](EOClient* c)
{
    // The client may disconnect if the password generation takes too long
    if (c->Connected())
    {
        PacketBuilder succeededReply(PACKET_ACCOUNT, PACKET_REPLY, 4);
        succeededReply.AddShort(ACCOUNT_CREATED);
        succeededReply.AddString("OK");

        c->Send(succeededReply);

        c->create_id = 0;
    }

    Console::Out("New account: %s", username.c_str());
};

client->server()->world->CreateAccount(client)
    ->OnSuccess(successCallback)
    ->OnFailure([](EOClient* c, int) { c->Close(); })
    ->Execute(std::make_shared<AccountCreateInfo>(std::move(accountInfo)));

Testing improvements

Vanilla EOSERV makes no attempts to provide code that can easily be unit tested. One of the secondary goals of etheos is to add a framework for more developer capabilities such as unit and integration testing.

Google test support was added to the etheos project, which allows for unit tests to be written. Although unit tests are now supported, the code is not designed to make unit testing very easy, so setting up all the required dependencies and mocking the appropriate classes still takes a huge amount of effort.

Integration test support was also added via the EOBot project from EndlessClient. Scripts are executed as part of an integration testing pipeline to verify some basic use cases of etheos.