diff --git a/src/network/protocols/server_lobby.cpp b/src/network/protocols/server_lobby.cpp
index 3516ffe03..aa3fd698b 100644
--- a/src/network/protocols/server_lobby.cpp
+++ b/src/network/protocols/server_lobby.cpp
@@ -249,6 +249,7 @@ ServerLobby::ServerLobby() : LobbyProtocol()
     m_game_mode.store(ServerConfig::m_server_mode);
     m_default_vote = new PeerVote();
     m_player_reports_table_exists = false;
+    m_grand_prix_rowid = -1;
     initDatabase();
 }   // ServerLobby
 
@@ -389,6 +390,51 @@ void ServerLobby::initServerStatsTable()
         ") WITHOUT ROWID;", country_table_name.c_str());
     easySQLQuery(query);
 
+    // Extra default table _grandprixresults:
+    std::string grandprixresults_table_name = std::string("v") +
+        StringUtils::toString(ServerConfig::m_server_db_version) + "_" +
+        ServerConfig::m_server_uid + "_grandprixresults";
+    query = StringUtils::insertValues(
+        "CREATE TABLE IF NOT EXISTS %s (\n"
+        "    num_races INTEGER NOT NULL, -- Number of races completed\n"
+        "    total_races INTEGER NOT NULL -- Total number of races\n"
+        ");", grandprixresults_table_name.c_str());
+    if(easySQLQuery(query)) {
+        m_server_grandprixresults_table = grandprixresults_table_name;
+    }
+
+    // Extra default table _raceresults:
+    std::string raceresults_table_name = std::string("v") +
+    StringUtils::toString(ServerConfig::m_server_db_version) + "_" +
+    ServerConfig::m_server_uid + "_raceresults";
+    query = StringUtils::insertValues(
+        "CREATE TABLE IF NOT EXISTS %s (\n"
+        "    race_finished TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, -- Time when race was completed\n"
+        "    grandprix_rowid INTEGER, -- ROWID of the grand prix this race is part of (optional)\n"
+        "    track_name TEXT NOT NULL, -- Number of laps completed\n"
+        "    num_laps INTEGER NOT NULL, -- Number of laps completed\n"
+        "    total_laps INTEGER NOT NULL, -- Total number of laps\n"
+        "    fastest_lap_time FLOAT NOT NULL, -- Fastest lap so far\n"
+        "    fastest_lap_player TEXT NOT NULL -- Fastest lap so far\n"
+        ");", raceresults_table_name.c_str());
+    if(easySQLQuery(query)) {
+        m_server_raceresults_table = raceresults_table_name;
+    }
+
+    // Extra default table _raceresults:
+    std::string playerresults_table_name = std::string("v") +
+    StringUtils::toString(ServerConfig::m_server_db_version) + "_" +
+    ServerConfig::m_server_uid + "_playerresults";
+    query = StringUtils::insertValues(
+        "CREATE TABLE IF NOT EXISTS %s (\n"
+        "    race_rowid INTEGER NOT NULL, -- ROWID of the race\n"
+        "    elapsed_time FLOAT NOT NULL, -- Total race time\n"
+        "    player_name TEXT NOT NULL -- Player name\n"
+        ");", playerresults_table_name.c_str());
+    if(easySQLQuery(query)) {
+        m_server_playerresults_table = playerresults_table_name;
+    }
+
     // Default views:
     // _full_stats
     // Full stats with ip in human readable format and time played of each
@@ -2884,6 +2930,7 @@ void ServerLobby::checkRaceFinished()
     RaceEventManager::get()->getProtocol()->requestTerminate();
     GameProtocol::lock()->requestTerminate();
 
+
     // Save race result before delete the world
     m_result_ns->clear();
     m_result_ns->addUInt8(LE_RACE_FINISHED);
@@ -2895,10 +2942,13 @@ void ServerLobby::checkRaceFinished()
         m_result_ns->addUInt32(fastest_lap);
         m_result_ns->encodeString(static_cast<LinearWorld*>(World::getWorld())
             ->getFastestLapKartName());
+        std::cout << static_cast<LinearWorld*>(World::getWorld())->getFastestLapKartName().c_str() << std::endl;
+        Log::info("RaceResults", "Fastest lap by %s", StringUtils::wideToUtf8(static_cast<LinearWorld*>(World::getWorld())->getFastestLapKartName()).c_str());
 
         // all gp tracks
         m_result_ns->addUInt8((uint8_t)m_game_setup->getTotalGrandPrixTracks())
             .addUInt8((uint8_t)m_game_setup->getAllTracks().size());
+        Log::info("RaceResults", "Track number %d / %d", m_game_setup->getAllTracks().size(), m_game_setup->getTotalGrandPrixTracks());
         for (const std::string& gp_track : m_game_setup->getAllTracks())
             m_result_ns->encodeString(gp_track);
 
@@ -2917,9 +2967,13 @@ void ServerLobby::checkRaceFinished()
                 overall_time = overall_time + player->getOverallTime();
                 player->setScore(cur_score);
                 player->setOverallTime(overall_time);
+                Log::info("RaceResults", "Score for %s from %d to %d (%f)", StringUtils::wideToUtf8(player->getName()).c_str(), last_score, cur_score, overall_time);
+            }
+            else {
+                Log::info("RaceResults", "Score for player %d from %d to %d (%f)", i, last_score, cur_score, overall_time);
             }
             m_result_ns->addUInt32(last_score).addUInt32(cur_score)
-                .addFloat(overall_time);            
+                .addFloat(overall_time);
         }
     }
     else if (RaceManager::get()->modeHasLaps())
@@ -2936,6 +2990,8 @@ void ServerLobby::checkRaceFinished()
         ranking_changes_indication = 1;
     m_result_ns->addUInt8(ranking_changes_indication);
 
+    writeRaceResults();
+
     if (ServerConfig::m_ranked)
     {
         computeNewRankings();
@@ -2944,6 +3000,148 @@ void ServerLobby::checkRaceFinished()
     m_state.store(WAIT_FOR_RACE_STOPPED);
 }   // checkRaceFinished
 
+int ServerLobby::getLastRowID(const std::string &table) {
+    int rowid = -1;
+    std::string query = StringUtils::insertValues("SELECT ROWID from %s ORDER BY ROWID DESC LIMIT 1;", table);
+
+    sqlite3_stmt* stmt = NULL;
+    int ret = sqlite3_prepare_v2(m_db, query.c_str(), -1, &stmt, 0);
+    if (ret == SQLITE_OK)
+    {
+        ret = sqlite3_step(stmt);
+        if (ret == SQLITE_ROW)
+        {
+            rowid = sqlite3_column_int(stmt, 0);
+        }
+        ret = sqlite3_finalize(stmt);
+        if (ret != SQLITE_OK)
+        {
+            Log::error("ServerLobby",
+                       "Error rowid database for query %s: %s",
+                       query.c_str(), sqlite3_errmsg(m_db));
+        }
+    }
+    else
+    {
+        Log::error("ServerLobby", "Error preparing database for query %s: %s",
+                   query.c_str(), sqlite3_errmsg(m_db));
+        return -1;
+    }
+    return rowid;
+
+}
+
+//-----------------------------------------------------------------------------
+/** Write the results of the race
+ */
+void ServerLobby::writeRaceResults()
+{
+#ifdef ENABLE_SQLITE3
+    std::string track_name = RaceManager::get()->getTrackName();
+    bool track_reverse = RaceManager::get()->getReverseTrack();
+    int player_count = RaceManager::get()->getNumPlayers();
+    int laps_number = RaceManager::get()->getNumLaps();
+
+    if (RaceManager::get()->getMinorMode() == RaceManager::MINOR_MODE_NORMAL_RACE) {
+        std::string query;
+        if (m_game_setup->isGrandPrix()) {
+            int race_number = m_game_setup->getAllTracks().size();
+            int total_races = m_game_setup->getTotalGrandPrixTracks();
+            if(race_number == 1) {
+                query = StringUtils::insertValues(
+                    "INSERT INTO %s "
+                    "(num_races, total_races) "
+                    "VALUES (%d, %d);",
+                    m_server_grandprixresults_table,
+                    race_number,
+                    total_races
+                );
+                if(easySQLQuery(query)) {
+                    m_grand_prix_rowid = getLastRowID(m_server_grandprixresults_table);
+                }
+            }
+            else {
+                query = StringUtils::insertValues(
+                    "UPDATE %s "
+                    "SET num_races = %d "
+                    "WHERE ROWID = %d;",
+                    m_server_grandprixresults_table,
+                    race_number,
+                    m_grand_prix_rowid
+                );
+                easySQLQuery(query);
+            }
+        }
+
+        if(m_grand_prix_rowid >= 0) {
+            query = StringUtils::insertValues(
+                "INSERT INTO %s "
+                "(grandprix_rowid, track_name, num_laps, total_laps, fastest_lap_time, fastest_lap_player) "
+                "VALUES (%d, \"%s\", %d, %d, %f, \"%s\");",
+                m_server_raceresults_table,
+                m_grand_prix_rowid,
+                track_name,
+                laps_number,
+                0,
+                0.,
+                ""
+            );
+        }
+        else {
+            query = StringUtils::insertValues(
+                "INSERT INTO %s "
+                "(track_name, num_laps, total_laps, fastest_lap_time, fastest_lap_player) "
+                "VALUES (\"%s\", %d, %d, %f, \"%s\");",
+                m_server_raceresults_table,
+                track_name,
+                laps_number,
+                0,
+                0.,
+                ""
+            );
+        }
+        if(easySQLQuery(query)) {
+            int race_rowid = getLastRowID(m_server_raceresults_table);
+            Log::info("RaceResults", "Row ID: %d", race_rowid);
+            if(race_rowid >= 0) {
+                for (int i = 0; i < player_count; i++) {
+                    double elapsed_time = RaceManager::get()->getKartRaceTime(i);
+                    std::string player_name = StringUtils::wideToUtf8(
+                        RaceManager::get()->getKartInfo(i).getPlayerName());
+                    //int grand_prix_rank = RaceManager::get()->getKartGPRank(i);
+                    query = StringUtils::insertValues(
+                        "INSERT INTO %s "
+                        "(race_rowid, elapsed_time, player_name) "
+                        "VALUES (%d, %f, ?);",
+                        m_server_playerresults_table,
+                        race_rowid,
+                        elapsed_time
+                    );
+                    easySQLQuery(query, [player_name](sqlite3_stmt* stmt) {
+                        if (sqlite3_bind_text(stmt, 1, player_name.c_str(),
+                                              -1, SQLITE_TRANSIENT) != SQLITE_OK)
+                        {
+                            Log::error("easySQLQuery", "Failed to bind %s.",
+                                       player_name.c_str());
+                        }
+                    });
+                }
+            }
+        }
+
+        if(m_game_setup->isGrandPrix()) {
+            int race_number = m_game_setup->getAllTracks().size();
+            int total_races = m_game_setup->getTotalGrandPrixTracks();
+
+            if(race_number == total_races) {
+                m_grand_prix_rowid = -1;
+            }
+        }
+    }
+#endif
+}
+
+
 //-----------------------------------------------------------------------------
 /** Compute the new player's rankings used in ranked servers
  */
diff --git a/src/network/protocols/server_lobby.hpp b/src/network/protocols/server_lobby.hpp
index 53d3aceda..780ef2d4e 100644
--- a/src/network/protocols/server_lobby.hpp
+++ b/src/network/protocols/server_lobby.hpp
@@ -85,6 +85,14 @@ private:
 
     std::string m_server_stats_table;
 
+    std::string m_server_grandprixresults_table;
+
+    std::string m_server_raceresults_table;
+
+    std::string m_server_playerresults_table;
+
+    int m_grand_prix_rowid;
+
     bool m_ip_ban_table_exists;
 
     bool m_ipv6_ban_table_exists;
@@ -377,6 +385,8 @@ private:
     void testBannedForOnlineId(STKPeer* peer, uint32_t online_id) const;
     void writeDisconnectInfoTable(STKPeer* peer);
     void writePlayerReport(Event* event);
+    int getLastRowID(const std::string &table);
+    void writeRaceResults();
     bool supportsAI();
     void updateAddons();
 public:
