41 changes: 41 additions & 0 deletions src/engine/server/sql_connector.cpp
@@ -0,0 +1,41 @@
#if defined(CONF_SQL)

#include <base/system.h>

#include "sql_connector.h"

CSqlServer** CSqlConnector::ms_ppSqlReadServers = 0;
CSqlServer** CSqlConnector::ms_ppSqlWriteServers = 0;

int CSqlConnector::ms_ReachableReadServer = 0;
int CSqlConnector::ms_ReachableWriteServer = 0;

CSqlConnector::CSqlConnector() :
m_pSqlServer(0),
m_NumReadRetries(0),
m_NumWriteRetries(0)
{}

bool CSqlConnector::ConnectSqlServer(bool ReadOnly)
{
ReadOnly ? ++m_NumReadRetries : ++m_NumWriteRetries;
int& ReachableServer = ReadOnly ? ms_ReachableReadServer : ms_ReachableWriteServer;
int NumServers = ReadOnly ? CSqlServer::ms_NumReadServer : CSqlServer::ms_NumWriteServer;

for (int i = ReachableServer, ID = ReachableServer; i < ReachableServer + NumServers && SqlServer(i % NumServers, ReadOnly); i++, ID = i % NumServers)
{
if (SqlServer(ID, ReadOnly) && SqlServer(ID, ReadOnly)->Connect())
{
m_pSqlServer = SqlServer(ID, ReadOnly);
ReachableServer = ID;
return true;
}
if (SqlServer(ID, ReadOnly))
dbg_msg("sql", "Warning: Unable to connect to Sql%sServer %d ('%s'), trying next...", ReadOnly ? "Read" : "Write", ID, SqlServer(ID, ReadOnly)->GetIP());
}
dbg_msg("sql", "FATAL ERROR: No Sql%sServers available", ReadOnly ? "Read" : "Write");
m_pSqlServer = 0;
return false;
}

#endif
47 changes: 47 additions & 0 deletions src/engine/server/sql_connector.h
@@ -0,0 +1,47 @@
#ifndef ENGINE_SERVER_SQL_CONNECTOR_H
#define ENGINE_SERVER_SQL_CONNECTOR_H

#include "sql_server.h"

enum
{
MAX_SQLSERVERS=15
};

// implementation to provide sqlservers
class CSqlConnector
{
public:
CSqlConnector();

CSqlServer* SqlServer(int i, bool ReadOnly = true) { return ReadOnly ? ms_ppSqlReadServers[i] : ms_ppSqlWriteServers[i]; }

// always returns the last connected sql-server
CSqlServer* SqlServer() { return m_pSqlServer; }

static void SetReadServers(CSqlServer** ppReadServers) { ms_ppSqlReadServers = ppReadServers; }
static void SetWriteServers(CSqlServer** ppWriteServers) { ms_ppSqlWriteServers = ppWriteServers; }

static void ResetReachable() { ms_ReachableReadServer = 0; ms_ReachableWriteServer = 0; }

bool ConnectSqlServer(bool ReadOnly = true);

bool MaxTriesReached(bool ReadOnly = true) { return ReadOnly ? m_NumReadRetries >= CSqlServer::ms_NumReadServer : m_NumWriteRetries >= CSqlServer::ms_NumWriteServer; }

private:

CSqlServer *m_pSqlServer;
static CSqlServer **ms_ppSqlReadServers;
static CSqlServer **ms_ppSqlWriteServers;

static int ms_NumReadServer;
static int ms_NumWriteServer;

static int ms_ReachableReadServer;
static int ms_ReachableWriteServer;

int m_NumReadRetries;
int m_NumWriteRetries;
};

#endif
213 changes: 213 additions & 0 deletions src/engine/server/sql_server.cpp
@@ -0,0 +1,213 @@
#if defined(CONF_SQL)

#include <base/system.h>
#include <engine/shared/protocol.h>
#include <engine/shared/config.h>

#include "sql_server.h"


int CSqlServer::ms_NumReadServer = 0;
int CSqlServer::ms_NumWriteServer = 0;

CSqlServer::CSqlServer(const char* pDatabase, const char* pPrefix, const char* pUser, const char* pPass, const char* pIp, int Port, bool ReadOnly, bool SetUpDb) :
m_Port(Port),
m_SetUpDB(SetUpDb)
{
str_copy(m_aDatabase, pDatabase, sizeof(m_aDatabase));
str_copy(m_aPrefix, pPrefix, sizeof(m_aPrefix));
str_copy(m_aUser, pUser, sizeof(m_aUser));
str_copy(m_aPass, pPass, sizeof(m_aPass));
str_copy(m_aIp, pIp, sizeof(m_aIp));

m_pDriver = 0;
m_pConnection = 0;
m_pResults = 0;
m_pStatement = 0;

ReadOnly ? ms_NumReadServer++ : ms_NumWriteServer++;

m_SqlLock = lock_create();
}

CSqlServer::~CSqlServer()
{
Lock();
try
{
if (m_pResults)
delete m_pResults;
if (m_pConnection)
delete m_pConnection;
dbg_msg("sql", "SQL connection disconnected");
}
catch (sql::SQLException &e)
{
dbg_msg("sql", "ERROR: No SQL connection");
}
UnLock();
lock_destroy(m_SqlLock);
}

bool CSqlServer::Connect()
{
Lock();

if (m_pDriver != NULL && m_pConnection != NULL)
{
try
{
// Connect to specific database
m_pConnection->setSchema(m_aDatabase);
}
catch (sql::SQLException &e)
{
dbg_msg("sql", "MySQL Error: %s", e.what());

dbg_msg("sql", "ERROR: SQL connection failed");
UnLock();
return false;
}
return true;
}

try
{
m_pDriver = 0;
m_pConnection = 0;
m_pStatement = 0;

sql::ConnectOptionsMap connection_properties;
connection_properties["hostName"] = sql::SQLString(m_aIp);
connection_properties["port"] = m_Port;
connection_properties["userName"] = sql::SQLString(m_aUser);
connection_properties["password"] = sql::SQLString(m_aPass);
connection_properties["OPT_CONNECT_TIMEOUT"] = 10;
connection_properties["OPT_READ_TIMEOUT"] = 10;
connection_properties["OPT_WRITE_TIMEOUT"] = 20;
connection_properties["OPT_RECONNECT"] = true;

// Create connection
m_pDriver = get_driver_instance();
m_pConnection = m_pDriver->connect(connection_properties);

// Create Statement
m_pStatement = m_pConnection->createStatement();

if (m_SetUpDB)
{
char aBuf[128];
// create database
str_format(aBuf, sizeof(aBuf), "CREATE DATABASE IF NOT EXISTS %s", m_aDatabase);
m_pStatement->execute(aBuf);
}

// Connect to specific database
m_pConnection->setSchema(m_aDatabase);
dbg_msg("sql", "sql connection established");
return true;
}
catch (sql::SQLException &e)
{
dbg_msg("sql", "MySQL Error: %s", e.what());
dbg_msg("sql", "ERROR: sql connection failed");
UnLock();
return false;
}
catch (const std::exception& ex)
{
// ...
dbg_msg("sql", "1 %s",ex.what());

}
catch (const std::string& ex)
{
// ...
dbg_msg("sql", "2 %s",ex.c_str());
}
catch( int )
{
dbg_msg("sql", "3 %s");
}
catch( float )
{
dbg_msg("sql", "4 %s");
}

catch( char[] )
{
dbg_msg("sql", "5 %s");
}

catch( char )
{
dbg_msg("sql", "6 %s");
}
catch (...)
{
dbg_msg("sql", "Unknown Error cause by the MySQL/C++ Connector, my advice compile server_debug and use it");

dbg_msg("sql", "ERROR: sql connection failed");
UnLock();
return false;
}
UnLock();
return false;
}

void CSqlServer::Disconnect()
{
UnLock();
}

void CSqlServer::CreateTables()
{
if (!Connect())
return;

try
{
char aBuf[1024];

// create tables
str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_race (Map VARCHAR(128) BINARY NOT NULL, Name VARCHAR(%d) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , Time FLOAT DEFAULT 0, Server CHAR(4), cp1 FLOAT DEFAULT 0, cp2 FLOAT DEFAULT 0, cp3 FLOAT DEFAULT 0, cp4 FLOAT DEFAULT 0, cp5 FLOAT DEFAULT 0, cp6 FLOAT DEFAULT 0, cp7 FLOAT DEFAULT 0, cp8 FLOAT DEFAULT 0, cp9 FLOAT DEFAULT 0, cp10 FLOAT DEFAULT 0, cp11 FLOAT DEFAULT 0, cp12 FLOAT DEFAULT 0, cp13 FLOAT DEFAULT 0, cp14 FLOAT DEFAULT 0, cp15 FLOAT DEFAULT 0, cp16 FLOAT DEFAULT 0, cp17 FLOAT DEFAULT 0, cp18 FLOAT DEFAULT 0, cp19 FLOAT DEFAULT 0, cp20 FLOAT DEFAULT 0, cp21 FLOAT DEFAULT 0, cp22 FLOAT DEFAULT 0, cp23 FLOAT DEFAULT 0, cp24 FLOAT DEFAULT 0, cp25 FLOAT DEFAULT 0, KEY (Map, Name)) CHARACTER SET utf8 ;", m_aPrefix, MAX_NAME_LENGTH);
executeSql(aBuf);

str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_teamrace (Map VARCHAR(128) BINARY NOT NULL, Name VARCHAR(%d) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, Time FLOAT DEFAULT 0, ID VARBINARY(16) NOT NULL, KEY Map (Map)) CHARACTER SET utf8 ;", m_aPrefix, MAX_NAME_LENGTH);
executeSql(aBuf);

str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_maps (Map VARCHAR(128) BINARY NOT NULL, Server VARCHAR(32) BINARY NOT NULL, Mapper VARCHAR(128) BINARY NOT NULL, Points INT DEFAULT 0, Stars INT DEFAULT 0, Timestamp TIMESTAMP, UNIQUE KEY Map (Map)) CHARACTER SET utf8 ;", m_aPrefix);
executeSql(aBuf);

str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_saves (Savegame TEXT CHARACTER SET utf8 BINARY NOT NULL, Map VARCHAR(128) BINARY NOT NULL, Code VARCHAR(128) BINARY NOT NULL, Timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, Server CHAR(4), UNIQUE KEY (Map, Code)) CHARACTER SET utf8 ;", m_aPrefix);
executeSql(aBuf);

str_format(aBuf, sizeof(aBuf), "CREATE TABLE IF NOT EXISTS %s_points (Name VARCHAR(%d) BINARY NOT NULL, Points INT DEFAULT 0, UNIQUE KEY Name (Name)) CHARACTER SET utf8 ;", m_aPrefix, MAX_NAME_LENGTH);
executeSql(aBuf);

dbg_msg("sql", "Tables were created successfully");
}
catch (sql::SQLException &e)
{
dbg_msg("sql", "MySQL Error: %s", e.what());
}

Disconnect();
}

void CSqlServer::executeSql(const char *pCommand)
{
m_pStatement->execute(pCommand);
}

void CSqlServer::executeSqlQuery(const char *pQuery)
{
if (m_pResults)
delete m_pResults;

// set it to 0, so exceptions raised from executeQuery can not make m_pResults point to invalid memory
m_pResults = 0;
m_pResults = m_pStatement->executeQuery(pQuery);
}

#endif
57 changes: 57 additions & 0 deletions src/engine/server/sql_server.h
@@ -0,0 +1,57 @@
#ifndef ENGINE_SERVER_SQL_SERVER_H
#define ENGINE_SERVER_SQL_SERVER_H

#include <mysql_connection.h>

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/statement.h>

class CSqlServer
{
public:
CSqlServer(const char* pDatabase, const char* pPrefix, const char* pUser, const char* pPass, const char* pIp, int Port, bool ReadOnly = true, bool SetUpDb = false);
~CSqlServer();

bool Connect();
void Disconnect();
void CreateTables();

void executeSql(const char* pCommand);
void executeSqlQuery(const char* pQuery);

sql::ResultSet* GetResults() { return m_pResults; }

const char* GetDatabase() { return m_aDatabase; }
const char* GetPrefix() { return m_aPrefix; }
const char* GetUser() { return m_aUser; }
const char* GetPass() { return m_aPass; }
const char* GetIP() { return m_aIp; }
int GetPort() { return m_Port; }

void Lock() { lock_wait(m_SqlLock); }
void UnLock() { lock_unlock(m_SqlLock); }

static int ms_NumReadServer;
static int ms_NumWriteServer;

private:
sql::Driver *m_pDriver;
sql::Connection *m_pConnection;
sql::Statement *m_pStatement;
sql::ResultSet *m_pResults;

// copy of config vars
char m_aDatabase[64];
char m_aPrefix[64];
char m_aUser[64];
char m_aPass[64];
char m_aIp[64];
int m_Port;

bool m_SetUpDB;

LOCK m_SqlLock;
};

#endif
145 changes: 145 additions & 0 deletions src/engine/server/sql_string_helpers.cpp
@@ -0,0 +1,145 @@
#include <cmath>
#include <cstring>
#include <ctime>
#include <base/system.h>

#include "sql_string_helpers.h"

void sqlstr::FuzzyString(char *pString)
{
char newString[32*4-1];
int pos = 0;

for(int i=0;i<64;i++)
{
if(!pString[i])
break;

newString[pos++] = pString[i];
if (pString[i] != '\\' && str_utf8_isstart(pString[i+1]))
newString[pos++] = '%';
}

newString[pos] = '\0';
strcpy(pString, newString);
}

// anti SQL injection
void sqlstr::ClearString(char *pString, int size)
{
char *newString = new char [size * 2 - 1];
int pos = 0;

for(int i=0;i<size;i++)
{
if(pString[i] == '\\')
{
newString[pos++] = '\\';
newString[pos++] = '\\';
}
else if(pString[i] == '\'')
{
newString[pos++] = '\\';
newString[pos++] = '\'';
}
else if(pString[i] == '"')
{
newString[pos++] = '\\';
newString[pos++] = '"';
}
else
{
newString[pos++] = pString[i];
}
}

newString[pos] = '\0';

strcpy(pString, newString);
delete [] newString;
}

void sqlstr::agoTimeToString(int agoTime, char agoString[])
{
char aBuf[20];
int times[7] =
{
60 * 60 * 24 * 365 ,
60 * 60 * 24 * 30 ,
60 * 60 * 24 * 7,
60 * 60 * 24 ,
60 * 60 ,
60 ,
1
};
char names[7][6] =
{
"year",
"month",
"week",
"day",
"hour",
"min",
"sec"
};

int seconds = 0;
char name[6];
int count = 0;
int i = 0;

// finding biggest match
for(i = 0; i<7; i++)
{
seconds = times[i];
strcpy(name, names[i]);

count = floor((float)agoTime/(float)seconds);
if(count != 0)
{
break;
}
}

if(count == 1)
{
str_format(aBuf, sizeof(aBuf), "%d %s", 1 , name);
}
else
{
str_format(aBuf, sizeof(aBuf), "%d %ss", count , name);
}
strcat(agoString, aBuf);

if (i + 1 < 7)
{
// getting second piece now
int seconds2 = times[i+1];
char name2[6];
strcpy(name2, names[i+1]);

// add second piece if it's greater than 0
int count2 = floor((float)(agoTime - (seconds * count)) / (float)seconds2);

if (count2 != 0)
{
if(count2 == 1)
{
str_format(aBuf, sizeof(aBuf), " and %d %s", 1 , name2);
}
else
{
str_format(aBuf, sizeof(aBuf), " and %d %ss", count2 , name2);
}
strcat(agoString, aBuf);
}
}
}

void sqlstr::getTimeStamp(char* dest, unsigned int size)
{
std::time_t rawtime;
std::time(&rawtime);

str_timestamp_ex(rawtime, dest, size, "%Y-%m-%d %H:%M:%S");
}
48 changes: 48 additions & 0 deletions src/engine/server/sql_string_helpers.h
@@ -0,0 +1,48 @@
#ifndef ENGINE_SERVER_SQL_STRING_HELPERS_H
#define ENGINE_SERVER_SQL_STRING_HELPERS_H

namespace sqlstr
{

void FuzzyString(char *pString);

// anti SQL injection
void ClearString(char *pString, int size = 32);

void agoTimeToString(int agoTime, char agoString[]);

void getTimeStamp(char* dest, unsigned int size);


template<unsigned int size>
class CSqlString
{
public:
CSqlString() {}

CSqlString(const char* pStr)
{
str_copy(m_aString, pStr, size);
str_copy(m_aClearString, pStr, size);
ClearString(m_aClearString, sizeof(m_aClearString));
}

const char* Str() const { return m_aString; }
const char* ClrStr() const { return m_aClearString; }

CSqlString& operator = (const char* pStr)
{
str_copy(m_aString, pStr, size);
str_copy(m_aClearString, pStr, size);
ClearString(m_aClearString, sizeof(m_aClearString));
return *this;
}

private:
char m_aString[size];
char m_aClearString[size * 2 - 1];
};

}

#endif
9 changes: 2 additions & 7 deletions src/engine/shared/config_variables.h
Expand Up @@ -219,16 +219,11 @@ MACRO_CONFIG_STR(SvScoreFolder, sv_score_folder, 32, "records", CFGFLAG_SERVER,

#if defined(CONF_SQL)
MACRO_CONFIG_INT(SvUseSQL, sv_use_sql, 0, 0, 1, CFGFLAG_SERVER, "Enables SQL DB instead of record file")
MACRO_CONFIG_STR(SvSqlUser, sv_sql_user, 32, "nameless", CFGFLAG_SERVER, "SQL User")
MACRO_CONFIG_STR(SvSqlPw, sv_sql_pw, 32, "tee", CFGFLAG_SERVER, "SQL Password")
MACRO_CONFIG_STR(SvSqlIp, sv_sql_ip, 32, "127.0.0.1", CFGFLAG_SERVER, "SQL Database IP")
MACRO_CONFIG_INT(SvSqlPort, sv_sql_port, 3306, 0, 65535, CFGFLAG_SERVER, "SQL Database port")
MACRO_CONFIG_INT(SvSqlCreateTables, sv_sql_create_tables, 1, 0, 1, CFGFLAG_SERVER, "Try to create the SQL tables when starting")
MACRO_CONFIG_STR(SvSqlDatabase, sv_sql_database, 16, "teeworlds", CFGFLAG_SERVER, "SQL Database name")
MACRO_CONFIG_STR(SvSqlServerName, sv_sql_servername, 5, "UNK", CFGFLAG_SERVER, "SQL Server name that is inserted into record table")
MACRO_CONFIG_STR(SvSqlPrefix, sv_sql_prefix, 16, "record", CFGFLAG_SERVER, "SQL Database table prefix")
MACRO_CONFIG_INT(SvSaveGames, sv_savegames, 1, 0, 1, CFGFLAG_SERVER, "Enables savegames (/save and /load)")
MACRO_CONFIG_INT(SvSaveGamesDelay, sv_savegames_delay, 60, 0, 10000, CFGFLAG_SERVER, "Delay in seconds for loading a savegame")

MACRO_CONFIG_STR(SvSqlFailureFile, sv_sql_failure_file, 64, "failed_sql.sql", CFGFLAG_SERVER, "File to store failed Sql-Inserts (ranks)")
#endif

MACRO_CONFIG_INT(SvDDRaceRules, sv_ddrace_rules, 1, 0, 1, CFGFLAG_SERVER, "Whether the default mod rules are displayed or not")
Expand Down
1 change: 1 addition & 0 deletions src/engine/shared/console.h
Expand Up @@ -4,6 +4,7 @@
#define ENGINE_SHARED_CONSOLE_H

#include <engine/console.h>
#include <base/math.h>
#include "memheap.h"

class CConsole : public IConsole
Expand Down
43 changes: 34 additions & 9 deletions src/engine/shared/network_server.cpp
Expand Up @@ -11,9 +11,36 @@
#include <engine/message.h>
#include <engine/shared/protocol.h>

//TODO: reduce dummy map size
const int DummyMapCrc = 0xbeae0b9f;
static const unsigned char g_aDummyMapData[] = {0x44,0x41,0x54,0x41,0x04,0x00,0x00,0x00,0x15,0x02,0x00,0x00,0xC8,0x01,0x00,0x00,0x05,0x00,0x00,0x00,0x07,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x4C,0x01,0x00,0x00,0x4D,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x05,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x06,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x00,0x00,0x00,0x28,0x00,0x00,0x00,0x6C,0x00,0x00,0x00,0xB0,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x44,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x42,0x00,0x00,0x00,0x98,0x00,0x00,0x00,0x10,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x14,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x04,0x00,0x3C,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x01,0x00,0x04,0x00,0x3C,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x64,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE5,0xED,0xE1,0xC7,0x80,0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x05,0x00,0x28,0x00,0x00,0x00,0x90,0x72,0xDE,0x98,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xE4,0xE1,0xF5,0xD1,0x80,0x80,0x80,0xF3,0x00,0x80,0x80,0x80,0x01,0x00,0x05,0x00,0x5C,0x00,0x00,0x00,0x90,0x72,0xDE,0x98,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x02,0x00,0x00,0x00,0x01,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00,0xE5,0xED,0xE1,0xC7,0x80,0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x20,0xA1,0xEA,0x00,0x63,0xE7,0x3D,0x44,0x0C,0x00,0x00,0x00,0x00,0x00,0x80,0x40,0x00,0x00,0x8D,0x42,0x00,0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x78,0x9C,0x63,0x38,0xFD,0xF9,0xBF,0xC3,0x8D,0x6F,0xFF,0x19,0x4C,0x79,0x18,0xC0,0x34,0x90,0x7F,0x40,0x9D,0x93,0x01,0xC4,0x07,0xD3,0x0D,0x0C,0x60,0x1C,0x07,0xA4,0x5A,0x80,0x78,0x1D,0x10,0xFF,0x67,0xC0,0xE4,0x9F,0x01,0xE2,0x17,0x50,0x36,0x36,0x3E,0x1C,0xB0,0xA0,0xB1,0xA1,0xF8,0x3F,0x10,0x80,0x84,0x60,0x34,0x00,0x3E,0x5E,0x23,0x81,0x78,0x9C,0x63,0x60,0x40,0x05,0x00,0x00,0x10,0x00,0x01};
const int DummyMapCrc = 0x6c760ac4;
unsigned char g_aDummyMapData[] = {
0x44, 0x41, 0x54, 0x41, 0x04, 0x00, 0x00, 0x00, 0x22, 0x01, 0x00, 0x00,
0x14, 0x01, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x05, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x7c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00,
0x1c, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00,
0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x0a, 0xc3, 0x52,
0xff, 0x7f, 0x00, 0x00, 0x20, 0x5c, 0xf6, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x05, 0x00, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xff, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x20, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x77, 0x00, 0x00, 0x00,
0x7c, 0x00, 0x00, 0x00, 0x78, 0x9c, 0x63, 0x64, 0x60, 0x60, 0x60, 0x44,
0xc2, 0x00, 0x00, 0x38, 0x00, 0x05
};


static SECURITY_TOKEN ToSecurityToken(const unsigned char* pData)
{
Expand Down Expand Up @@ -210,7 +237,7 @@ int CNetServer::TryAcceptClient(NETADDR &Addr, SECURITY_TOKEN SecurityToken, boo
{
char aBuf[128];
str_format(aBuf, sizeof(aBuf), "Only %d players with the same IP are allowed", m_MaxClientsPerIP);
CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, sizeof(aBuf), SecurityToken);
CNetBase::SendControlMsg(m_Socket, &Addr, 0, NET_CTRLMSG_CLOSE, aBuf, str_length(aBuf) + 1, SecurityToken);
return -1; // failed to add client
}

Expand Down Expand Up @@ -283,7 +310,7 @@ void CNetServer::SendMsgs(NETADDR &Addr, const CMsgPacker *Msgs[], int num)

//
m_Construct.m_DataSize = (int)(pChunkData-m_Construct.m_aChunkData);
CNetBase::SendPacket(m_Socket, &Addr, &m_Construct, GetToken(Addr));
CNetBase::SendPacket(m_Socket, &Addr, &m_Construct, NET_SECURITY_TOKEN_UNSUPPORTED);
}

// connection-less msg packet without token-support
Expand Down Expand Up @@ -339,8 +366,7 @@ void CNetServer::OnPreConnMsg(NETADDR &Addr, CNetPacketConstruct &Packet)
}

// simulate accept
SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC,
sizeof(SECURITY_TOKEN_MAGIC), NET_SECURITY_TOKEN_UNSUPPORTED);
SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, NULL, 0, NET_SECURITY_TOKEN_UNSUPPORTED);

// Begin vanilla compatible token handshake
// The idea is to pack a security token in the gametick
Expand Down Expand Up @@ -410,8 +436,7 @@ void CNetServer::OnPreConnMsg(NETADDR &Addr, CNetPacketConstruct &Packet)
else
{
// accept client directy
SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, SECURITY_TOKEN_MAGIC,
sizeof(SECURITY_TOKEN_MAGIC), NET_SECURITY_TOKEN_UNSUPPORTED);
SendControl(Addr, NET_CTRLMSG_CONNECTACCEPT, NULL, 0, NET_SECURITY_TOKEN_UNSUPPORTED);

TryAcceptClient(Addr, NET_SECURITY_TOKEN_UNSUPPORTED);
}
Expand Down
2 changes: 1 addition & 1 deletion src/engine/shared/packer.cpp
Expand Up @@ -125,7 +125,7 @@ const char *CUnpacker::GetString(int SanitizeType)
m_pCurrent++;
if(m_pCurrent == m_pEnd)
{
m_Error = 1;;
m_Error = 1;
return "";
}
}
Expand Down
4 changes: 3 additions & 1 deletion src/game/client/components/console.cpp
Expand Up @@ -224,6 +224,7 @@ void CGameConsole::CInstance::OnInput(IInput::CEvent Event)
{
m_CompletionChosen = -1;
str_copy(m_aCompletionBuffer, m_Input.GetString(), sizeof(m_aCompletionBuffer));
m_CompletionRenderOffset = 0.0f;
}

// find the current command
Expand Down Expand Up @@ -609,7 +610,8 @@ void CGameConsole::OnMessage(int MsgType, void *pRawMsg)

bool CGameConsole::OnInput(IInput::CEvent Event)
{
if(m_ConsoleState != CONSOLE_OPEN && m_ConsoleState != CONSOLE_OPENING)
// accept input when opening, but not at first frame to discard the input that caused the console to open
if(m_ConsoleState != CONSOLE_OPEN && (m_ConsoleState != CONSOLE_OPENING || m_StateChangeEnd == TimeNow()+m_StateChangeDuration))
return false;
if((Event.m_Key >= KEY_F1 && Event.m_Key <= KEY_F12) || (Event.m_Key >= KEY_F13 && Event.m_Key <= KEY_F24))
return false;
Expand Down
10 changes: 7 additions & 3 deletions src/game/client/components/menus.cpp
Expand Up @@ -926,11 +926,15 @@ int CMenus::Render()

m_MouseSlow = false;

static bool s_First = true;
if(s_First)
static int s_Frame = 0;
if(s_Frame == 0)
{
s_Frame++;
}
else if(s_Frame == 1)
{
m_pClient->m_pSounds->Enqueue(CSounds::CHN_MUSIC, SOUND_MENU);
s_First = false;
s_Frame++;
m_DoubleClickIndex = -1;

if(g_Config.m_UiPage == PAGE_INTERNET)
Expand Down
8 changes: 4 additions & 4 deletions src/game/client/components/menus_settings.cpp
Expand Up @@ -417,7 +417,7 @@ void CMenus::RenderSettingsTee(CUIRect MainView)
Label.VSplitLeft(230.0f, &Label, 0);
RenderTools()->DrawUIRect(&Label, vec4(1.0f, 1.0f, 1.0f, 0.25f), CUI::CORNER_ALL, 10.0f);
RenderTools()->RenderTee(CAnimState::GetIdle(), &OwnSkinInfo, 0, vec2(1, 0), vec2(Label.x+30.0f, Label.y+28.0f));
Label.HSplitTop(15.0f, 0, &Label);;
Label.HSplitTop(15.0f, 0, &Label);
Label.VSplitLeft(70.0f, 0, &Label);
UI()->DoLabelScaled(&Label, Skin, 14.0f, -1, 150.0f);

Expand Down Expand Up @@ -1224,7 +1224,7 @@ void CMenus::RenderLanguageSelection(CUIRect MainView)
{
str_copy(g_Config.m_ClLanguagefile, s_Languages[s_SelectedLanguage].m_FileName, sizeof(g_Config.m_ClLanguagefile));
g_Localization.Load(s_Languages[s_SelectedLanguage].m_FileName, Storage(), Console());

// Load Font
static CFont *pDefaultFont = 0;
char aFilename[512];
Expand Down Expand Up @@ -1950,7 +1950,7 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView)
UI()->DoLabelScaled(&Label, paLabels[s], 15.0f, -1);
}
}

{
static float s_Map = 0.0f;
aRects[1].HSplitTop(25.0f, &Background, &aRects[1]);
Expand All @@ -1965,7 +1965,7 @@ void CMenus::RenderSettingsDDNet(CUIRect MainView)
g_Config.m_ClBackgroundShowTilesLayers ^= 1;
}
}

MainView.HSplitTop(30.0f, &Label, &Miscellaneous);
UI()->DoLabelScaled(&Label, Localize("Miscellaneous"), 20.0f, -1);
Miscellaneous.VMargin(5.0f, &Miscellaneous);
Expand Down
1 change: 1 addition & 0 deletions src/game/client/components/particles.cpp
Expand Up @@ -137,6 +137,7 @@ void CParticles::OnRender()
if(Client()->State() < IClient::STATE_ONLINE)
return;

set_new_tick();
static int64 LastTime = 0;
int64 t = time_get();

Expand Down
2 changes: 1 addition & 1 deletion src/game/client/components/players.cpp
Expand Up @@ -559,7 +559,7 @@ void CPlayers::RenderPlayer(

float PhysSize = 28.0f;

vec2 OldPos = initPos + ExDirection * PhysSize * 1.5f;;
vec2 OldPos = initPos + ExDirection * PhysSize * 1.5f;
vec2 NewPos = OldPos;

bool doBreak = false;
Expand Down
2 changes: 1 addition & 1 deletion src/game/client/components/scoreboard.cpp
Expand Up @@ -454,7 +454,7 @@ void CScoreboard::RenderScoreboard(float x, float y, float w, int Team, const ch
else
str_format(aId, sizeof(aId)," %d: ", pInfo->m_ClientID);
str_append(aId, m_pClient->m_aClients[pInfo->m_ClientID].m_aName,sizeof(aId));
Cursor.m_LineWidth = NameLength+3;
Cursor.m_LineWidth = NameLength+8;
TextRender()->TextEx(&Cursor, aId, -1);
}
else
Expand Down
2 changes: 1 addition & 1 deletion src/game/client/gameclient.cpp
Expand Up @@ -1512,7 +1512,7 @@ void CGameClient::OnPredict()
if(i == m_Snap.m_LocalClientID)
continue;
if(!(distance(World.m_apCharacters[i]->m_Pos, ProjPos) < Radius+ProximityRadius))
continue;;
continue;

CCharacterCore *pTarget = World.m_apCharacters[i];

Expand Down
41 changes: 32 additions & 9 deletions src/game/editor/editor.cpp
Expand Up @@ -700,7 +700,7 @@ void CEditor::RenderBackground(CUIRect View, int Texture, float Size, float Brig
Graphics()->QuadsEnd();
}

int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool isDegree, bool isHex)
int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool isDegree, bool isHex, int corners)
{
// logic
static float s_Value;
Expand Down Expand Up @@ -804,7 +804,7 @@ int CEditor::UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, in
str_format(aBuf, sizeof(aBuf),"#%06X", Current);
else
str_format(aBuf, sizeof(aBuf),"%d", Current);
RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, 0), CUI::CORNER_ALL, 5.0f);
RenderTools()->DrawUIRect(pRect, GetButtonColor(pID, 0), corners, 5.0f);
pRect->y += pRect->h/2.0f-7.0f;
UI()->DoLabel(pRect, aBuf, 10, 0, -1);
}
Expand Down Expand Up @@ -2677,15 +2677,18 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int *
Shifter.VSplitRight(10.0f, &Shifter, &Inc);
Shifter.VSplitLeft(10.0f, &Dec, &Shifter);
str_format(aBuf, sizeof(aBuf),"%d", pProps[i].m_Value);
RenderTools()->DrawUIRect(&Shifter, color, 0, 0.0f);
UI()->DoLabel(&Shifter, aBuf, 10.0f, 0, -1);

if(DoButton_ButtonDec(&pIDs[i], 0, 0, &Dec, 0, "Decrease"))
int NewValue = UiDoValueSelector((char *) &pIDs[i], &Shifter, "", pProps[i].m_Value, pProps[i].m_Min, pProps[i].m_Max, 1, 1.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.", false, false, 0);
if (NewValue != pProps[i].m_Value)
{
*pNewVal = NewValue;
Change = i;
}
if(DoButton_ButtonDec((char *) &pIDs[i] +1, 0, 0, &Dec, 0, "Decrease"))
{
*pNewVal = pProps[i].m_Value-1;
Change = i;
}
if(DoButton_ButtonInc(((char *)&pIDs[i])+1, 0, 0, &Inc, 0, "Increase"))
if(DoButton_ButtonInc(((char *)&pIDs[i])+2, 0, 0, &Inc, 0, "Increase"))
{
*pNewVal = pProps[i].m_Value+1;
Change = i;
Expand Down Expand Up @@ -2717,16 +2720,36 @@ int CEditor::DoProperties(CUIRect *pToolBox, CProperty *pProps, int *pIDs, int *
}
else if(pProps[i].m_Type == PROPTYPE_ANGLE_SCROLL)
{
CUIRect Inc, Dec;
Shifter.VSplitRight(10.0f, &Shifter, &Inc);
Shifter.VSplitLeft(10.0f, &Dec, &Shifter);
bool Shift = Input()->KeyIsPressed(KEY_LSHIFT) || Input()->KeyIsPressed(KEY_RSHIFT);
int Value = pProps[i].m_Value;
if (!Shift && UI()->MouseButton(0) && UI()->ActiveItem() == &pIDs[i])
const void *activeItem = UI()->ActiveItem();
if (!Shift && UI()->MouseButton(0) && (activeItem == &pIDs[i] || activeItem == &pIDs[i]+1 || activeItem == &pIDs[i]+2))
Value = (Value / 45) * 45;
int NewValue = UiDoValueSelector(&pIDs[i], &Shifter, "", Value, pProps[i].m_Min, Shift ? pProps[i].m_Max : 315, Shift ? 1 : 45, Shift ? 1.0f : 10.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.");
int NewValue = UiDoValueSelector(&pIDs[i], &Shifter, "", Value, pProps[i].m_Min, Shift ? pProps[i].m_Max : 315, Shift ? 1 : 45, Shift ? 1.0f : 10.0f, "Use left mouse button to drag and change the value. Hold shift to be more precise. Rightclick to edit as text.",false,false,0);
if(NewValue != pProps[i].m_Value)
{
*pNewVal = NewValue;
Change = i;
}
if (DoButton_ButtonDec(&pIDs[i]+1, 0, 0, &Dec, 0, "Decrease"))
{
if (!Shift)
*pNewVal = pProps[i].m_Value - 45;
else
*pNewVal = pProps[i].m_Value - 1;
Change = i;
}
if (DoButton_ButtonInc(&pIDs[i]+ 2, 0, 0, &Inc, 0, "Increase"))
{
if (!Shift)
*pNewVal = pProps[i].m_Value + 45;
else
*pNewVal = pProps[i].m_Value + 1;
Change = i;
}
}
else if(pProps[i].m_Type == PROPTYPE_COLOR)
{
Expand Down
2 changes: 1 addition & 1 deletion src/game/editor/editor.h
Expand Up @@ -925,7 +925,7 @@ class CEditor : public IEditor
void UiInvokePopupMenu(void *pID, int Flags, float X, float Y, float W, float H, int (*pfnFunc)(CEditor *pEditor, CUIRect Rect), void *pExtra=0);
void UiDoPopupMenu();

int UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool isDegree=false, bool isHex=false);
int UiDoValueSelector(void *pID, CUIRect *pRect, const char *pLabel, int Current, int Min, int Max, int Step, float Scale, const char *pToolTip, bool isDegree=false, bool isHex=false, int corners = CUI::CORNER_ALL);

static int PopupGroup(CEditor *pEditor, CUIRect View);
static int PopupLayer(CEditor *pEditor, CUIRect View);
Expand Down
4 changes: 2 additions & 2 deletions src/game/editor/popups.cpp
Expand Up @@ -1443,8 +1443,8 @@ int CEditor::PopupSpeedup(CEditor *pEditor, CUIRect View)
};

CProperty aProps[] = {
{"Force", pEditor->m_SpeedupForce, PROPTYPE_INT_SCROLL, 0, 255},
{"Max Speed", pEditor->m_SpeedupMaxSpeed, PROPTYPE_INT_SCROLL, 0, 255},
{"Force", pEditor->m_SpeedupForce, PROPTYPE_INT_STEP, 0, 255},
{"Max Speed", pEditor->m_SpeedupMaxSpeed, PROPTYPE_INT_STEP, 0, 255},
{"Angle", pEditor->m_SpeedupAngle, PROPTYPE_ANGLE_SCROLL, 0, 359},
{0},
};
Expand Down
2 changes: 1 addition & 1 deletion src/game/gamecore.cpp
Expand Up @@ -106,7 +106,7 @@ void CCharacterCore::Reset()
void CCharacterCore::Tick(bool UseInput, bool IsClient)
{
float PhysSize = 28.0f;
int MapIndex = Collision()->GetPureMapIndex(m_Pos);;
int MapIndex = Collision()->GetPureMapIndex(m_Pos);
int MapIndexL = Collision()->GetPureMapIndex(vec2(m_Pos.x + (28/2)+4,m_Pos.y));
int MapIndexR = Collision()->GetPureMapIndex(vec2(m_Pos.x - (28/2)-4,m_Pos.y));
int MapIndexT = Collision()->GetPureMapIndex(vec2(m_Pos.x,m_Pos.y + (28/2)+4));
Expand Down
10 changes: 7 additions & 3 deletions src/game/server/entities/character.cpp
Expand Up @@ -1590,20 +1590,24 @@ void CCharacter::HandleTiles(int Index)
}

// unlock team
else if((m_TileIndex == TILE_UNLOCK_TEAM) || (m_TileFIndex == TILE_UNLOCK_TEAM))
else if(((m_TileIndex == TILE_UNLOCK_TEAM) || (m_TileFIndex == TILE_UNLOCK_TEAM)) && Teams()->TeamLocked(Team()))
{
Teams()->SetTeamLock(Team(), false);

for(int i = 0; i < MAX_CLIENTS; i++)
if(Teams()->m_Core.Team(i) == Team())
GameServer()->SendChatTarget(i, "Your team was unlocked by an unlock team tile");
}

// solo part
if(((m_TileIndex == TILE_SOLO_START) || (m_TileFIndex == TILE_SOLO_START)) && !Teams()->m_Core.GetSolo(m_pPlayer->GetCID()))
{
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You are now in a solo part.");
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You are now in a solo part");
SetSolo(true);
}
else if(((m_TileIndex == TILE_SOLO_END) || (m_TileFIndex == TILE_SOLO_END)) && Teams()->m_Core.GetSolo(m_pPlayer->GetCID()))
{
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You are now out of the solo part.");
GameServer()->SendChatTarget(GetPlayer()->GetCID(), "You are now out of the solo part");
SetSolo(false);
}

Expand Down
5 changes: 4 additions & 1 deletion src/game/server/gamecontext.cpp
Expand Up @@ -2705,8 +2705,11 @@ void CGameContext::OnMapChange(char *pNewMapName, int MapNameSize)
str_copy(m_aDeleteTempfile, aTemp, sizeof(m_aDeleteTempfile));
}

void CGameContext::OnShutdown()
void CGameContext::OnShutdown(bool FullShutdown)
{
if (FullShutdown)
Score()->OnShutdown();

DeleteTempfile();
Console()->ResetServerGameSettings();
Layers()->Dest();
Expand Down
2 changes: 1 addition & 1 deletion src/game/server/gamecontext.h
Expand Up @@ -197,7 +197,7 @@ class CGameContext : public IGameServer
virtual void OnInit();
virtual void OnConsoleInit();
virtual void OnMapChange(char *pNewMapName, int MapNameSize);
virtual void OnShutdown();
virtual void OnShutdown(bool FullShutdown = false);

virtual void OnTick();
virtual void OnPreSnap();
Expand Down
3 changes: 3 additions & 0 deletions src/game/server/score.h
Expand Up @@ -65,6 +65,9 @@ class IScore

virtual void SaveTeam(int Team, const char* Code, int ClientID, const char* Server) = 0;
virtual void LoadTeam(const char* Code, int ClientID) = 0;

// called when the server is shut down but not on mapchange/reload
virtual void OnShutdown() = 0;
};

#endif
5 changes: 5 additions & 0 deletions src/game/server/score/file_score.cpp
Expand Up @@ -351,3 +351,8 @@ void CFileScore::LoadTeam(const char* Code, int ClientID)
str_format(aBuf, sizeof(aBuf), "Save-function not supported in file based servers");
GameServer()->SendChatTarget(ClientID, aBuf);
}

void CFileScore::OnShutdown()
{
;
}
2 changes: 2 additions & 0 deletions src/game/server/score/file_score.h
Expand Up @@ -84,6 +84,8 @@ class CFileScore: public IScore
virtual void RandomUnfinishedMap(int ClientID, int stars);
virtual void SaveTeam(int Team, const char* Code, int ClientID, const char* Server);
virtual void LoadTeam(const char* Code, int ClientID);

virtual void OnShutdown();
};

#endif
2,497 changes: 1,095 additions & 1,402 deletions src/game/server/score/sql_score.cpp

Large diffs are not rendered by default.

250 changes: 144 additions & 106 deletions src/game/server/score/sql_score.h
Expand Up @@ -4,157 +4,195 @@
#ifndef GAME_SERVER_SQLSCORE_H
#define GAME_SERVER_SQLSCORE_H

#include <mysql_connection.h>
#include <exception>

#include <cppconn/driver.h>
#include <cppconn/exception.h>
#include <cppconn/statement.h>
#include <base/system.h>
#include <engine/console.h>
#include <engine/server/sql_connector.h>
#include <engine/server/sql_string_helpers.h>

#include "../score.h"

class CSqlScore: public IScore

class CGameContextError : public std::runtime_error
{
CGameContext *m_pGameServer;
IServer *m_pServer;
public:
CGameContextError(const char* pMsg) : std::runtime_error(pMsg) {}
};

sql::Driver *m_pDriver;
sql::Connection *m_pConnection;
sql::Statement *m_pStatement;
sql::ResultSet *m_pResults;

// copy of config vars
const char* m_pDatabase;
const char* m_pPrefix;
const char* m_pUser;
const char* m_pPass;
const char* m_pIp;
char m_aMap[64];
int m_Port;

CGameContext *GameServer()
// generic implementation to provide gameserver and server
struct CSqlData
{
CSqlData() : m_Map(ms_pMap)
{
return m_pGameServer;
m_Instance = ms_Instance;
}
IServer *Server()

virtual ~CSqlData() {}

bool isGameContextVaild() const
{
return m_pServer;
return m_Instance == ms_Instance && ms_GameContextAvailable;
}

static void MapInfoThread(void *pUser);
static void MapVoteThread(void *pUser);
static void CheckBirthdayThread(void *pUser);
static void LoadScoreThread(void *pUser);
static void SaveScoreThread(void *pUser);
static void SaveTeamScoreThread(void *pUser);
static void ShowRankThread(void *pUser);
static void ShowTop5Thread(void *pUser);
static void ShowTeamRankThread(void *pUser);
static void ShowTeamTop5Thread(void *pUser);
static void ShowTimesThread(void *pUser);
static void ShowPointsThread(void *pUser);
static void ShowTopPointsThread(void *pUser);
static void RandomMapThread(void *pUser);
static void RandomUnfinishedMapThread(void *pUser);
static void SaveTeamThread(void *pUser);
static void LoadTeamThread(void *pUser);

void Init();

bool Connect();
void Disconnect();

void FuzzyString(char *pString);
// anti SQL injection
void ClearString(char *pString, int size = 32);

void NormalizeMapname(char *pString);
CGameContext* GameServer() const { return isGameContextVaild() ? ms_pGameServer : throw CGameContextError("[CSqlData]: GameServer() unavailable."); }
IServer* Server() const { return isGameContextVaild() ? ms_pServer : throw CGameContextError("[CSqlData]: Server() unavailable."); }
CPlayerData* PlayerData(int ID) const { return isGameContextVaild() ? &ms_pPlayerData[ID] : throw CGameContextError("[CSqlData]: PlayerData() unavailable."); }

public:
sqlstr::CSqlString<128> m_Map;

CSqlScore(CGameContext *pGameServer);
~CSqlScore();
// counter to keep track to which instance of GameServer this object belongs to.
int m_Instance;

virtual void CheckBirthday(int ClientID);
virtual void LoadScore(int ClientID);
virtual void MapInfo(int ClientID, const char* MapName);
virtual void MapVote(int ClientID, const char* MapName);
virtual void SaveScore(int ClientID, float Time,
float CpTime[NUM_CHECKPOINTS]);
virtual void SaveTeamScore(int* aClientIDs, unsigned int Size, float Time);
virtual void ShowRank(int ClientID, const char* pName, bool Search = false);
virtual void ShowTeamRank(int ClientID, const char* pName, bool Search = false);
virtual void ShowTimes(int ClientID, const char* pName, int Debut = 1);
virtual void ShowTimes(int ClientID, int Debut = 1);
virtual void ShowTop5(IConsole::IResult *pResult, int ClientID,
void *pUserData, int Debut = 1);
virtual void ShowTeamTop5(IConsole::IResult *pResult, int ClientID,
void *pUserData, int Debut = 1);
virtual void ShowPoints(int ClientID, const char* pName, bool Search = false);
virtual void ShowTopPoints(IConsole::IResult *pResult, int ClientID,
void *pUserData, int Debut = 1);
virtual void RandomMap(int ClientID, int stars);
virtual void RandomUnfinishedMap(int ClientID, int stars);
virtual void SaveTeam(int Team, const char* Code, int ClientID, const char* Server);
virtual void LoadTeam(const char* Code, int ClientID);
static void agoTimeToString(int agoTime, char agoString[]);
static CGameContext *ms_pGameServer;
static IServer *ms_pServer;
static CPlayerData *ms_pPlayerData;
static const char *ms_pMap;

static bool ms_GameContextAvailable;
// contains the instancecount of the current GameServer
static int ms_Instance;
};

struct CSqlExecData
{
CSqlExecData(bool (*pFuncPtr) (CSqlServer*, const CSqlData *, bool), CSqlData *pSqlData, bool ReadOnly = true) :
m_pFuncPtr(pFuncPtr),
m_pSqlData(pSqlData),
m_ReadOnly(ReadOnly)
{
++ms_InstanceCount;
}
~CSqlExecData()
{
--ms_InstanceCount;
}

bool (*m_pFuncPtr) (CSqlServer*, const CSqlData *, bool);
CSqlData *m_pSqlData;
bool m_ReadOnly;

// keeps track of score-threads
static int ms_InstanceCount;
};

struct CSqlMapData
struct CSqlPlayerData : CSqlData
{
CSqlScore *m_pSqlData;
int m_ClientID;
char m_aMap[128];
sqlstr::CSqlString<MAX_NAME_LENGTH> m_Name;
};

struct CSqlScoreData
// used for mapvote and mapinfo
struct CSqlMapData : CSqlData
{
int m_ClientID;

sqlstr::CSqlString<128> m_RequestedMap;
char m_aFuzzyMap[128];
};

struct CSqlScoreData : CSqlData
{
CSqlScore *m_pSqlData;
int m_ClientID;
#if defined(CONF_FAMILY_WINDOWS)
char m_aName[16]; // Don't edit this, or all your teeth will fall http://bugs.mysql.com/bug.php?id=50046
#else
char m_aName[MAX_NAME_LENGTH * 2 - 1];
#endif

sqlstr::CSqlString<MAX_NAME_LENGTH> m_Name;

float m_Time;
float m_aCpCurrent[NUM_CHECKPOINTS];
int m_Num;
bool m_Search;
char m_aRequestingPlayer[MAX_NAME_LENGTH];
char m_aRequestingPlayer [MAX_NAME_LENGTH];
};

struct CSqlTeamScoreData
struct CSqlTeamScoreData : CSqlData
{
CSqlScore *m_pSqlData;
unsigned int m_Size;
int m_aClientIDs[MAX_CLIENTS];
#if defined(CONF_FAMILY_WINDOWS)
char m_aNames[16][MAX_CLIENTS]; // Don't edit this, or all your teeth will fall http://bugs.mysql.com/bug.php?id=50046
#else
char m_aNames[MAX_NAME_LENGTH * 2 - 1][MAX_CLIENTS];
#endif
sqlstr::CSqlString<MAX_NAME_LENGTH> m_aNames [MAX_CLIENTS];

float m_Time;
float m_aCpCurrent[NUM_CHECKPOINTS];
int m_Num;
bool m_Search;
char m_aRequestingPlayer[MAX_NAME_LENGTH];
};

struct CSqlTeamSave
struct CSqlTeamSave : CSqlData
{
virtual ~CSqlTeamSave();

int m_Team;
int m_ClientID;
char m_Code[128];
sqlstr::CSqlString<128> m_Code;
char m_Server[5];
CSqlScore *m_pSqlData;
};

struct CSqlTeamLoad
struct CSqlTeamLoad : CSqlData
{
char m_Code[128];
sqlstr::CSqlString<128> m_Code;
int m_ClientID;
CSqlScore *m_pSqlData;
};

class CSqlScore: public IScore
{
CGameContext *GameServer() { return m_pGameServer; }
IServer *Server() { return m_pServer; }

CGameContext *m_pGameServer;
IServer *m_pServer;

static void ExecSqlFunc(void *pUser);

static bool Init(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure);

char m_aMap[64];

static LOCK ms_FailureFileLock;

static bool CheckBirthdayThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool MapInfoThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool MapVoteThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool LoadScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool SaveScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool SaveTeamScoreThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowTeamRankThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowTeamTop5Thread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowTimesThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool ShowTopPointsThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool RandomMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool RandomUnfinishedMapThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool SaveTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);
static bool LoadTeamThread(CSqlServer* pSqlServer, const CSqlData *pGameData, bool HandleFailure = false);

public:

CSqlScore(CGameContext *pGameServer);
~CSqlScore();

virtual void CheckBirthday(int ClientID);
virtual void LoadScore(int ClientID);
virtual void MapInfo(int ClientID, const char* MapName);
virtual void MapVote(int ClientID, const char* MapName);
virtual void SaveScore(int ClientID, float Time,
float CpTime[NUM_CHECKPOINTS]);
virtual void SaveTeamScore(int* aClientIDs, unsigned int Size, float Time);
virtual void ShowRank(int ClientID, const char* pName, bool Search = false);
virtual void ShowTeamRank(int ClientID, const char* pName, bool Search = false);
virtual void ShowTimes(int ClientID, const char* pName, int Debut = 1);
virtual void ShowTimes(int ClientID, int Debut = 1);
virtual void ShowTop5(IConsole::IResult *pResult, int ClientID,
void *pUserData, int Debut = 1);
virtual void ShowTeamTop5(IConsole::IResult *pResult, int ClientID,
void *pUserData, int Debut = 1);
virtual void ShowPoints(int ClientID, const char* pName, bool Search = false);
virtual void ShowTopPoints(IConsole::IResult *pResult, int ClientID,
void *pUserData, int Debut = 1);
virtual void RandomMap(int ClientID, int stars);
virtual void RandomUnfinishedMap(int ClientID, int stars);
virtual void SaveTeam(int Team, const char* Code, int ClientID, const char* Server);
virtual void LoadTeam(const char* Code, int ClientID);

virtual void OnShutdown();
};

#endif
6 changes: 3 additions & 3 deletions src/game/version.h
Expand Up @@ -3,8 +3,8 @@
#ifndef GAME_VERSION_H
#define GAME_VERSION_H
#include "generated/nethash.cpp"
#define GAME_VERSION "0.6.3, 10.1.1"
#define GAME_VERSION "0.6.3, 10.2"
#define GAME_NETVERSION "0.6 626fce9a778df4d4"
static const char GAME_RELEASE_VERSION[8] = "10.1.1";
#define CLIENT_VERSIONNR 1001
static const char GAME_RELEASE_VERSION[8] = "10.2";
#define CLIENT_VERSIONNR 1002
#endif
80 changes: 80 additions & 0 deletions src/tools/dummy_map.cpp
@@ -0,0 +1,80 @@
#include <base/system.h>
#include <base/tl/array.h>
#include <engine/shared/datafile.h>
#include <engine/storage.h>
#include <game/mapitems.h>

void CreateEmptyMap(IStorage *pStorage)
{
CDataFileWriter Writer;
if(!Writer.Open(pStorage, "maps/dummy3.map"))
{
dbg_msg("empty_map", "couldn't open map file 'dummy3.map' for writing");
return;
}
CMapItemGroup_v1 Group;
Group.m_Version = 1;
Group.m_OffsetX = 0;
Group.m_OffsetY = 0;
Group.m_ParallaxX = 0;
Group.m_ParallaxY = 0;
Group.m_StartLayer = 0;
Group.m_NumLayers = 2;
Writer.AddItem(MAPITEMTYPE_GROUP, 0, sizeof(Group), &Group);

CMapItemLayerTilemap GameLayer;
GameLayer.m_Layer.m_Version = 0; // Not set by the official client.
GameLayer.m_Layer.m_Type = LAYERTYPE_TILES;
GameLayer.m_Layer.m_Flags = 0;
GameLayer.m_Version = 2;
GameLayer.m_Width = 2;
GameLayer.m_Height = 2;
GameLayer.m_Flags = TILESLAYERFLAG_GAME;
GameLayer.m_Color.r = 0;
GameLayer.m_Color.g = 0;
GameLayer.m_Color.b = 0;
GameLayer.m_Color.a = 0;
GameLayer.m_ColorEnv = -1;
GameLayer.m_ColorEnvOffset = 0;
GameLayer.m_Image = -1;
GameLayer.m_Data = 0;
Writer.AddItem(MAPITEMTYPE_LAYER, 0, sizeof(GameLayer) - sizeof(GameLayer.m_aName), &GameLayer);

CMapItemLayerTilemap Layer;
Layer.m_Layer.m_Version = 0;
Layer.m_Layer.m_Type = LAYERTYPE_TILES;
Layer.m_Layer.m_Flags = 0;
Layer.m_Version = 2;
Layer.m_Width = 2;
Layer.m_Height = 2;
Layer.m_Flags = 0;
Layer.m_Color.r = 0;
Layer.m_Color.g = 0;
Layer.m_Color.b = 0;
Layer.m_Color.a = 255;
Layer.m_ColorEnv = -1;
Layer.m_ColorEnvOffset = 0;
Layer.m_Image = -1;
Layer.m_Data = 0;
Writer.AddItem(MAPITEMTYPE_LAYER, 1, sizeof(Layer) - sizeof(Layer.m_aName), &Layer);

CTile Tiles[4];
for(int i = 0; i < 4; i++)
{
Tiles[i].m_Index = 1;
Tiles[i].m_Flags = 0;
Tiles[i].m_Skip = 0;
Tiles[i].m_Reserved = 0;
}
Writer.AddData(sizeof(Tiles), &Tiles);

Writer.Finish();
}

int main(int argc, const char **argv)
{
dbg_logger_stdout();
IStorage *pStorage = CreateStorage("Teeworlds", IStorage::STORAGETYPE_SERVER, argc, argv);
CreateEmptyMap(pStorage);
return 0;
}