Merge pull request #1344 from woogerboy21/clientid_commit_res
Commit to resolve requests made by @Daenyth discussed after the commit.
This commit is contained in:
commit
c64bc3fb81
10 changed files with 109 additions and 94 deletions
|
@ -35,7 +35,6 @@
|
||||||
#include "QtNetwork/QNetworkInterface"
|
#include "QtNetwork/QNetworkInterface"
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
|
|
||||||
|
|
||||||
#include "main.h"
|
#include "main.h"
|
||||||
#include "window_main.h"
|
#include "window_main.h"
|
||||||
#include "dlg_settings.h"
|
#include "dlg_settings.h"
|
||||||
|
@ -100,7 +99,7 @@ bool settingsValid()
|
||||||
!settingsCache->getPicsPath().isEmpty();
|
!settingsCache->getPicsPath().isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
void generateClientID()
|
QString const generateClientID()
|
||||||
{
|
{
|
||||||
QString macList;
|
QString macList;
|
||||||
foreach(QNetworkInterface interface, QNetworkInterface::allInterfaces())
|
foreach(QNetworkInterface interface, QNetworkInterface::allInterfaces())
|
||||||
|
@ -110,7 +109,7 @@ void generateClientID()
|
||||||
macList += interface.hardwareAddress() + ".";
|
macList += interface.hardwareAddress() + ".";
|
||||||
}
|
}
|
||||||
QString strClientID = QCryptographicHash::hash(macList.toUtf8(), QCryptographicHash::Sha1).toHex().right(15);
|
QString strClientID = QCryptographicHash::hash(macList.toUtf8(), QCryptographicHash::Sha1).toHex().right(15);
|
||||||
settingsCache->setClientID(strClientID);
|
return strClientID;
|
||||||
}
|
}
|
||||||
|
|
||||||
int main(int argc, char *argv[])
|
int main(int argc, char *argv[])
|
||||||
|
@ -220,7 +219,7 @@ int main(int argc, char *argv[])
|
||||||
QIcon icon(":/resources/appicon.svg");
|
QIcon icon(":/resources/appicon.svg");
|
||||||
ui.setWindowIcon(icon);
|
ui.setWindowIcon(icon);
|
||||||
|
|
||||||
generateClientID(); //generate the users client id
|
settingsCache->setClientID(generateClientID());
|
||||||
qDebug() << "ClientID In Cache: " << settingsCache->getClientID();
|
qDebug() << "ClientID In Cache: " << settingsCache->getClientID();
|
||||||
|
|
||||||
ui.showMaximized();
|
ui.showMaximized();
|
||||||
|
|
|
@ -7,14 +7,14 @@ class QSystemTrayIcon;
|
||||||
class SoundEngine;
|
class SoundEngine;
|
||||||
|
|
||||||
extern CardDatabase *db;
|
extern CardDatabase *db;
|
||||||
|
|
||||||
extern QSystemTrayIcon *trayIcon;
|
extern QSystemTrayIcon *trayIcon;
|
||||||
extern QTranslator *translator;
|
extern QTranslator *translator;
|
||||||
extern const QString translationPrefix;
|
extern const QString translationPrefix;
|
||||||
extern QString translationPath;
|
extern QString translationPath;
|
||||||
|
|
||||||
void installNewTranslator();
|
void installNewTranslator();
|
||||||
void generateClientID();
|
|
||||||
|
QString const generateClientID();
|
||||||
|
|
||||||
bool settingsValid();
|
bool settingsValid();
|
||||||
|
|
||||||
|
|
|
@ -316,6 +316,9 @@ void MainWindow::loginError(Response::ResponseCode r, QString reasonStr, quint32
|
||||||
actRegister();
|
actRegister();
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case Response::RespClientIdRequired:
|
||||||
|
QMessageBox::critical(this, tr("Error"), tr("This server requires client ID's. Your client is either failing to generate an ID or you are running a modified client.\nPlease close and reopen your client to try again."));
|
||||||
|
break;
|
||||||
case Response::RespAccountNotActivated: {
|
case Response::RespAccountNotActivated: {
|
||||||
bool ok = false;
|
bool ok = false;
|
||||||
QString token = QInputDialog::getText(this, tr("Account activation"), tr("Your account has not been activated yet.\nYou need to provide the activation token received in the activation email"), QLineEdit::Normal, QString(), &ok);
|
QString token = QInputDialog::getText(this, tr("Account activation"), tr("Your account has not been activated yet.\nYou need to provide the activation token received in the activation email"), QLineEdit::Normal, QString(), &ok);
|
||||||
|
|
|
@ -35,7 +35,7 @@ message Response {
|
||||||
RespActivationAccepted = 31; // Server accepted a reg user activation token
|
RespActivationAccepted = 31; // Server accepted a reg user activation token
|
||||||
RespActivationFailed = 32; // Server didn't accept a reg user activation token
|
RespActivationFailed = 32; // Server didn't accept a reg user activation token
|
||||||
RespRegistrationAcceptedNeedsActivation = 33; // Server accepted cient registration, but it will need token activation
|
RespRegistrationAcceptedNeedsActivation = 33; // Server accepted cient registration, but it will need token activation
|
||||||
RespClientIDRequired = 34; // Server require's client to generate and send its client id before allowing access
|
RespClientIdRequired = 34; // Server requires client to generate and send its client id before allowing access
|
||||||
}
|
}
|
||||||
enum ResponseType {
|
enum ResponseType {
|
||||||
JOIN_ROOM = 1000;
|
JOIN_ROOM = 1000;
|
||||||
|
|
|
@ -170,9 +170,10 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session, QString
|
||||||
event.mutable_user_info()->CopyFrom(session->copyUserInfo(true, true, true));
|
event.mutable_user_info()->CopyFrom(session->copyUserInfo(true, true, true));
|
||||||
locker.unlock();
|
locker.unlock();
|
||||||
|
|
||||||
// check if client id exists (older client compatibility)
|
|
||||||
if (clientid.isEmpty()){
|
if (clientid.isEmpty()){
|
||||||
// client id is empty, either out dated client or client has been modified
|
// client id is empty, either out dated client or client has been modified
|
||||||
|
if (getClientIdRequired())
|
||||||
|
return ClientIdRequired;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// update users database table with client id
|
// update users database table with client id
|
||||||
|
|
|
@ -28,7 +28,7 @@ class GameEventContainer;
|
||||||
class CommandContainer;
|
class CommandContainer;
|
||||||
class Command_JoinGame;
|
class Command_JoinGame;
|
||||||
|
|
||||||
enum AuthenticationResult { NotLoggedIn, PasswordRight, UnknownUser, WouldOverwriteOldSession, UserIsBanned, UsernameInvalid, RegistrationRequired, UserIsInactive };
|
enum AuthenticationResult { NotLoggedIn, PasswordRight, UnknownUser, WouldOverwriteOldSession, UserIsBanned, UsernameInvalid, RegistrationRequired, UserIsInactive, ClientIdRequired };
|
||||||
|
|
||||||
class Server : public QObject
|
class Server : public QObject
|
||||||
{
|
{
|
||||||
|
@ -56,6 +56,7 @@ public:
|
||||||
virtual QString getLoginMessage() const { return QString(); }
|
virtual QString getLoginMessage() const { return QString(); }
|
||||||
|
|
||||||
virtual bool getGameShouldPing() const { return false; }
|
virtual bool getGameShouldPing() const { return false; }
|
||||||
|
virtual bool getClientIdRequired() const { return false; }
|
||||||
virtual int getPingClockInterval() const { return 0; }
|
virtual int getPingClockInterval() const { return 0; }
|
||||||
virtual int getMaxGameInactivityTime() const { return 9999999; }
|
virtual int getMaxGameInactivityTime() const { return 9999999; }
|
||||||
virtual int getMaxPlayerInactivityTime() const { return 9999999; }
|
virtual int getMaxPlayerInactivityTime() const { return 9999999; }
|
||||||
|
|
|
@ -381,12 +381,16 @@ Response::ResponseCode Server_ProtocolHandler::cmdPing(const Command_Ping & /*cm
|
||||||
Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd, ResponseContainer &rc)
|
Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd, ResponseContainer &rc)
|
||||||
{
|
{
|
||||||
QString userName = QString::fromStdString(cmd.user_name()).simplified();
|
QString userName = QString::fromStdString(cmd.user_name()).simplified();
|
||||||
QString clientid = QString::fromStdString(cmd.clientid()).simplified();
|
QString clientId = QString::fromStdString(cmd.clientid()).simplified();
|
||||||
|
|
||||||
if (userName.isEmpty() || (userInfo != 0))
|
if (userName.isEmpty() || (userInfo != 0))
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
|
if (clientId.isEmpty())
|
||||||
|
return Response::RespContextError;
|
||||||
|
|
||||||
QString reasonStr;
|
QString reasonStr;
|
||||||
int banSecondsLeft = 0;
|
int banSecondsLeft = 0;
|
||||||
AuthenticationResult res = server->loginUser(this, userName, QString::fromStdString(cmd.password()), reasonStr, banSecondsLeft, clientid);
|
AuthenticationResult res = server->loginUser(this, userName, QString::fromStdString(cmd.password()), reasonStr, banSecondsLeft, clientId);
|
||||||
switch (res) {
|
switch (res) {
|
||||||
case UserIsBanned: {
|
case UserIsBanned: {
|
||||||
Response_Login *re = new Response_Login;
|
Response_Login *re = new Response_Login;
|
||||||
|
@ -405,6 +409,7 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd
|
||||||
return Response::RespUsernameInvalid;
|
return Response::RespUsernameInvalid;
|
||||||
}
|
}
|
||||||
case RegistrationRequired: return Response::RespRegistrationRequired;
|
case RegistrationRequired: return Response::RespRegistrationRequired;
|
||||||
|
case ClientIdRequired: return Response::RespClientIdRequired;
|
||||||
case UserIsInactive: return Response::RespAccountNotActivated;
|
case UserIsInactive: return Response::RespAccountNotActivated;
|
||||||
default: authState = res;
|
default: authState = res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,9 @@ clientkeepalive=1
|
||||||
; considered disconnected; default is 15
|
; considered disconnected; default is 15
|
||||||
max_player_inactivity_time=15
|
max_player_inactivity_time=15
|
||||||
|
|
||||||
|
; More modern clients generate client IDs based on specific client side information. Enable this option to
|
||||||
|
' require that clients report the client ID in order to log into the server. Default is false
|
||||||
|
requireclientid=false
|
||||||
|
|
||||||
[authentication]
|
[authentication]
|
||||||
|
|
||||||
|
|
|
@ -142,6 +142,7 @@ bool Servatrice::initServer()
|
||||||
{
|
{
|
||||||
serverName = settingsCache->value("server/name", "My Cockatrice server").toString();
|
serverName = settingsCache->value("server/name", "My Cockatrice server").toString();
|
||||||
serverId = settingsCache->value("server/id", 0).toInt();
|
serverId = settingsCache->value("server/id", 0).toInt();
|
||||||
|
clientIdRequired = settingsCache->value("server/requireclientid",0).toBool();
|
||||||
bool regServerOnly = settingsCache->value("authentication/regonly", 0).toBool();
|
bool regServerOnly = settingsCache->value("authentication/regonly", 0).toBool();
|
||||||
|
|
||||||
const QString authenticationMethodStr = settingsCache->value("authentication/method").toString();
|
const QString authenticationMethodStr = settingsCache->value("authentication/method").toString();
|
||||||
|
@ -160,7 +161,8 @@ bool Servatrice::initServer()
|
||||||
qDebug() << "Authenticating method: none";
|
qDebug() << "Authenticating method: none";
|
||||||
authenticationMethod = AuthenticationNone;
|
authenticationMethod = AuthenticationNone;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
qDebug() << "Client ID Required: " << clientIdRequired;
|
||||||
bool maxUserLimitEnabled = settingsCache->value("security/enable_max_user_limit", false).toBool();
|
bool maxUserLimitEnabled = settingsCache->value("security/enable_max_user_limit", false).toBool();
|
||||||
qDebug() << "Maximum user limit enabled: " << maxUserLimitEnabled;
|
qDebug() << "Maximum user limit enabled: " << maxUserLimitEnabled;
|
||||||
|
|
||||||
|
|
|
@ -43,125 +43,126 @@ class ServerSocketInterface;
|
||||||
class IslInterface;
|
class IslInterface;
|
||||||
|
|
||||||
class Servatrice_GameServer : public QTcpServer {
|
class Servatrice_GameServer : public QTcpServer {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
private:
|
private:
|
||||||
Servatrice *server;
|
Servatrice *server;
|
||||||
QList<Servatrice_ConnectionPool *> connectionPools;
|
QList<Servatrice_ConnectionPool *> connectionPools;
|
||||||
public:
|
public:
|
||||||
Servatrice_GameServer(Servatrice *_server, int _numberPools, const QSqlDatabase &_sqlDatabase, QObject *parent = 0);
|
Servatrice_GameServer(Servatrice *_server, int _numberPools, const QSqlDatabase &_sqlDatabase, QObject *parent = 0);
|
||||||
~Servatrice_GameServer();
|
~Servatrice_GameServer();
|
||||||
protected:
|
protected:
|
||||||
#if QT_VERSION < 0x050000
|
#if QT_VERSION < 0x050000
|
||||||
void incomingConnection(int socketDescriptor);
|
void incomingConnection(int socketDescriptor);
|
||||||
#else
|
#else
|
||||||
void incomingConnection(qintptr socketDescriptor);
|
void incomingConnection(qintptr socketDescriptor);
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
class Servatrice_IslServer : public QTcpServer {
|
class Servatrice_IslServer : public QTcpServer {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
private:
|
private:
|
||||||
Servatrice *server;
|
Servatrice *server;
|
||||||
QSslCertificate cert;
|
QSslCertificate cert;
|
||||||
QSslKey privateKey;
|
QSslKey privateKey;
|
||||||
public:
|
public:
|
||||||
Servatrice_IslServer(Servatrice *_server, const QSslCertificate &_cert, const QSslKey &_privateKey, QObject *parent = 0)
|
Servatrice_IslServer(Servatrice *_server, const QSslCertificate &_cert, const QSslKey &_privateKey, QObject *parent = 0)
|
||||||
: QTcpServer(parent), server(_server), cert(_cert), privateKey(_privateKey) { }
|
: QTcpServer(parent), server(_server), cert(_cert), privateKey(_privateKey) { }
|
||||||
protected:
|
protected:
|
||||||
#if QT_VERSION < 0x050000
|
#if QT_VERSION < 0x050000
|
||||||
void incomingConnection(int socketDescriptor);
|
void incomingConnection(int socketDescriptor);
|
||||||
#else
|
#else
|
||||||
void incomingConnection(qintptr socketDescriptor);
|
void incomingConnection(qintptr socketDescriptor);
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
class ServerProperties {
|
class ServerProperties {
|
||||||
public:
|
public:
|
||||||
int id;
|
int id;
|
||||||
QSslCertificate cert;
|
QSslCertificate cert;
|
||||||
QString hostname;
|
QString hostname;
|
||||||
QHostAddress address;
|
QHostAddress address;
|
||||||
int gamePort;
|
int gamePort;
|
||||||
int controlPort;
|
int controlPort;
|
||||||
|
|
||||||
ServerProperties(int _id, const QSslCertificate &_cert, const QString &_hostname, const QHostAddress &_address, int _gamePort, int _controlPort)
|
ServerProperties(int _id, const QSslCertificate &_cert, const QString &_hostname, const QHostAddress &_address, int _gamePort, int _controlPort)
|
||||||
: id(_id), cert(_cert), hostname(_hostname), address(_address), gamePort(_gamePort), controlPort(_controlPort) { }
|
: id(_id), cert(_cert), hostname(_hostname), address(_address), gamePort(_gamePort), controlPort(_controlPort) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
class Servatrice : public Server
|
class Servatrice : public Server
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum AuthenticationMethod { AuthenticationNone, AuthenticationSql, AuthenticationPassword };
|
enum AuthenticationMethod { AuthenticationNone, AuthenticationSql, AuthenticationPassword };
|
||||||
private slots:
|
private slots:
|
||||||
void statusUpdate();
|
void statusUpdate();
|
||||||
void shutdownTimeout();
|
void shutdownTimeout();
|
||||||
protected:
|
protected:
|
||||||
void doSendIslMessage(const IslMessage &msg, int serverId);
|
void doSendIslMessage(const IslMessage &msg, int serverId);
|
||||||
private:
|
private:
|
||||||
enum DatabaseType { DatabaseNone, DatabaseMySql };
|
enum DatabaseType { DatabaseNone, DatabaseMySql };
|
||||||
AuthenticationMethod authenticationMethod;
|
AuthenticationMethod authenticationMethod;
|
||||||
DatabaseType databaseType;
|
DatabaseType databaseType;
|
||||||
QTimer *pingClock, *statusUpdateClock;
|
QTimer *pingClock, *statusUpdateClock;
|
||||||
Servatrice_GameServer *gameServer;
|
Servatrice_GameServer *gameServer;
|
||||||
Servatrice_IslServer *islServer;
|
Servatrice_IslServer *islServer;
|
||||||
QString serverName;
|
QString serverName;
|
||||||
mutable QMutex loginMessageMutex;
|
mutable QMutex loginMessageMutex;
|
||||||
QString loginMessage;
|
QString loginMessage;
|
||||||
QString dbPrefix;
|
QString dbPrefix;
|
||||||
Servatrice_DatabaseInterface *servatriceDatabaseInterface;
|
Servatrice_DatabaseInterface *servatriceDatabaseInterface;
|
||||||
int serverId;
|
int serverId;
|
||||||
int uptime;
|
int uptime;
|
||||||
QMutex txBytesMutex, rxBytesMutex;
|
QMutex txBytesMutex, rxBytesMutex;
|
||||||
quint64 txBytes, rxBytes;
|
quint64 txBytes, rxBytes;
|
||||||
int maxGameInactivityTime, maxPlayerInactivityTime;
|
int maxGameInactivityTime, maxPlayerInactivityTime;
|
||||||
int maxUsersPerAddress, messageCountingInterval, maxMessageCountPerInterval, maxMessageSizePerInterval, maxGamesPerUser, commandCountingInterval, maxCommandCountPerInterval, pingClockInterval;
|
int maxUsersPerAddress, messageCountingInterval, maxMessageCountPerInterval, maxMessageSizePerInterval, maxGamesPerUser, commandCountingInterval, maxCommandCountPerInterval, pingClockInterval;
|
||||||
|
|
||||||
QString shutdownReason;
|
QString shutdownReason;
|
||||||
int shutdownMinutes;
|
int shutdownMinutes;
|
||||||
QTimer *shutdownTimer;
|
QTimer *shutdownTimer;
|
||||||
bool isFirstShutdownMessage;
|
bool isFirstShutdownMessage, clientIdRequired;
|
||||||
|
|
||||||
mutable QMutex serverListMutex;
|
mutable QMutex serverListMutex;
|
||||||
QList<ServerProperties> serverList;
|
QList<ServerProperties> serverList;
|
||||||
void updateServerList();
|
void updateServerList();
|
||||||
|
|
||||||
QMap<int, IslInterface *> islInterfaces;
|
QMap<int, IslInterface *> islInterfaces;
|
||||||
public slots:
|
public slots:
|
||||||
void scheduleShutdown(const QString &reason, int minutes);
|
void scheduleShutdown(const QString &reason, int minutes);
|
||||||
void updateLoginMessage();
|
void updateLoginMessage();
|
||||||
public:
|
public:
|
||||||
Servatrice(QObject *parent = 0);
|
Servatrice(QObject *parent = 0);
|
||||||
~Servatrice();
|
~Servatrice();
|
||||||
bool initServer();
|
bool initServer();
|
||||||
QString getServerName() const { return serverName; }
|
QString getServerName() const { return serverName; }
|
||||||
QString getLoginMessage() const { QMutexLocker locker(&loginMessageMutex); return loginMessage; }
|
QString getLoginMessage() const { QMutexLocker locker(&loginMessageMutex); return loginMessage; }
|
||||||
bool getGameShouldPing() const { return true; }
|
bool getGameShouldPing() const { return true; }
|
||||||
int getPingClockInterval() const { return pingClockInterval; }
|
bool getClientIdRequired() const { return clientIdRequired; }
|
||||||
int getMaxGameInactivityTime() const { return maxGameInactivityTime; }
|
int getPingClockInterval() const { return pingClockInterval; }
|
||||||
int getMaxPlayerInactivityTime() const { return maxPlayerInactivityTime; }
|
int getMaxGameInactivityTime() const { return maxGameInactivityTime; }
|
||||||
int getMaxUsersPerAddress() const { return maxUsersPerAddress; }
|
int getMaxPlayerInactivityTime() const { return maxPlayerInactivityTime; }
|
||||||
int getMessageCountingInterval() const { return messageCountingInterval; }
|
int getMaxUsersPerAddress() const { return maxUsersPerAddress; }
|
||||||
int getMaxMessageCountPerInterval() const { return maxMessageCountPerInterval; }
|
int getMessageCountingInterval() const { return messageCountingInterval; }
|
||||||
int getMaxMessageSizePerInterval() const { return maxMessageSizePerInterval; }
|
int getMaxMessageCountPerInterval() const { return maxMessageCountPerInterval; }
|
||||||
int getMaxGamesPerUser() const { return maxGamesPerUser; }
|
int getMaxMessageSizePerInterval() const { return maxMessageSizePerInterval; }
|
||||||
|
int getMaxGamesPerUser() const { return maxGamesPerUser; }
|
||||||
int getCommandCountingInterval() const { return commandCountingInterval; }
|
int getCommandCountingInterval() const { return commandCountingInterval; }
|
||||||
int getMaxCommandCountPerInterval() const { return maxCommandCountPerInterval; }
|
int getMaxCommandCountPerInterval() const { return maxCommandCountPerInterval; }
|
||||||
AuthenticationMethod getAuthenticationMethod() const { return authenticationMethod; }
|
AuthenticationMethod getAuthenticationMethod() const { return authenticationMethod; }
|
||||||
QString getDbPrefix() const { return dbPrefix; }
|
QString getDbPrefix() const { return dbPrefix; }
|
||||||
int getServerId() const { return serverId; }
|
int getServerId() const { return serverId; }
|
||||||
int getUsersWithAddress(const QHostAddress &address) const;
|
int getUsersWithAddress(const QHostAddress &address) const;
|
||||||
QList<ServerSocketInterface *> getUsersWithAddressAsList(const QHostAddress &address) const;
|
QList<ServerSocketInterface *> getUsersWithAddressAsList(const QHostAddress &address) const;
|
||||||
void incTxBytes(quint64 num);
|
void incTxBytes(quint64 num);
|
||||||
void incRxBytes(quint64 num);
|
void incRxBytes(quint64 num);
|
||||||
void addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterface *databaseInterface);
|
void addDatabaseInterface(QThread *thread, Servatrice_DatabaseInterface *databaseInterface);
|
||||||
|
|
||||||
bool islConnectionExists(int serverId) const;
|
bool islConnectionExists(int serverId) const;
|
||||||
void addIslInterface(int serverId, IslInterface *interface);
|
void addIslInterface(int serverId, IslInterface *interface);
|
||||||
void removeIslInterface(int serverId);
|
void removeIslInterface(int serverId);
|
||||||
QReadWriteLock islLock;
|
QReadWriteLock islLock;
|
||||||
|
|
||||||
QList<ServerProperties> getServerList() const;
|
QList<ServerProperties> getServerList() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue