/* GNU General Public License version 3 notice Copyright (C) 2012 Mihawk . All rights reserved. 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 3 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, see < http://www.gnu.org/licenses/ >. */ #include "ServerQuery.h" #include #include #include #include #include #include // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ServerQuery::ServerQuery(QObject *parent) : QObject(parent), mySocket(new QUdpSocket(this)), myPort(27500), myActiveFlag(false), myCurrentPingIndex(0) { connect(mySocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketError(QAbstractSocket::SocketError))); connect(mySocket, SIGNAL(readyRead()), SLOT(parseServerInfo())); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ServerQuery::~ServerQuery() { } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool ServerQuery::query() { myActiveFlag = true; if(myAddress.isNull() || !myPort) { emit error(HostLookupError); myActiveFlag = false; return false; } if(mySocket->isOpen()) mySocket->close(); mySocket->connectToHost(myAddress, myPort); if(!mySocket->waitForConnected(100)) { emit error(ConnectionError); myActiveFlag = false; return false; } if(mySocket->write("\xff\xff\xff\xffstatus 23\n", 14) == -1) { emit error(SendError); myActiveFlag = false; return false; } myPingTime.restart(); return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - const QHostAddress &ServerQuery::address() const { return myAddress; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - quint16 ServerQuery::port() const { return myPort; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool ServerQuery::setAddress(const QHostAddress &address, quint16 port) { myAddress = address; myPort = port; return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool ServerQuery::setAddress(const QString &address, quint16 port) { QHostInfo hi = QHostInfo::fromName(address); if(hi.error() != QHostInfo::NoError) { emit error(HostLookupError); return false; } myAddress = hi.addresses().at(0); myPort = port; return true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void ServerQuery::parseServerInfo() { QByteArray serverData = mySocket->readAll(); // qDebug() << mySocket->peerAddress() << mySocket->peerPort(); if(serverData.isEmpty()) { emit error(EmptyResponseError); myActiveFlag = false; return; } myPings[myCurrentPingIndex++ & 7] = myPingTime.elapsed(); if(!serverData.startsWith("\xff\xff\xff\xffn")) { emit error(InvalidResponseError); myActiveFlag = false; return; } /* Parse server rules */ myRules.clear(); QString infoString(serverData.data()+5); QStringList splitInfoString = infoString.split("\n", QString::SkipEmptyParts); int i; QStringList rules = splitInfoString.at(0).split("\\", QString::SkipEmptyParts); if((rules.size() % 2) != 0) { emit error(InvalidInfoStringError); myActiveFlag = false; return; } ServerRule rule; for(i = 0; i < rules.size(); i += 2) { rule.rule = rules.at(i); rule.value = rules.at(i+1); /* Proxy detection */ if(rule.rule == "*QTV") { myIsProxyFlag = true; } else if(rule.rule == "*version") { QString value = rule.value; if(value.startsWith("qwfwd", Qt::CaseInsensitive) || value.startsWith("QTV", Qt::CaseInsensitive) || value.startsWith("2.91")) myIsProxyFlag = true; } /* Adjust FPS */ if(rule.rule == "maxfps" && myPing > 0) myPing += rule.value.toUInt() * 12 / 77; myRules.append(rule); } /* Parse player info */ myPlayers.clear(); Player player; QRegExp regex("^([-0-9]+)\\s+([-0-9]+)\\s+([-0-9]+)\\s+([-0-9]+)\\s+\"([^\"]*)\"\\s+\"([^\"]*)\"\\s+([0-9]+)\\s+([0-9]+)(?:\\s+\"([^\"]*)\")?$"); QStringList playerInfo; for(i = 1; i < splitInfoString.size(); i++) { if(regex.indexIn(splitInfoString.at(i)) == -1) { emit error(InvalidPlayerInfoError); continue; } playerInfo = regex.capturedTexts(); player.id = playerInfo.at(1).toInt(); player.frags = playerInfo.at(2).toInt(); player.time = playerInfo.at(3).toInt(); player.ping = playerInfo.at(4).toInt(); player.name = playerInfo.at(5); player.skin = playerInfo.at(6); player.topColor = playerInfo.at(7).toInt(); player.bottomColor = playerInfo.at(8).toInt(); player.team = playerInfo.at(9); if(player.name.startsWith("\\s\\")) { player.spectator = true; player.team = "(spec)"; player.name = player.name.replace(QRegExp("^\\\\s\\\\"), ""); if(player.ping < 0) player.ping = -player.ping; } else { player.spectator = false; } myPlayers.append(player); } myActiveFlag = false; emit finished(); return; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool ServerQuery::isActive() const { return myActiveFlag; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void ServerQuery::socketError(QAbstractSocket::SocketError) { emit error(UnknownError); myActiveFlag = false; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - int ServerQuery::ping() const { quint16 avgPing = 0; quint8 divisor; if(!myCurrentPingIndex) return 0xffff; if(myCurrentPingIndex < 8) { for(int i = 0; i < myCurrentPingIndex; ++i) avgPing += myPings[i]; divisor = myCurrentPingIndex; } else { for(int i = 0; i < 8; ++i) avgPing += myPings[i]; divisor = 8; } return avgPing / divisor; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - PlayerList ServerQuery::playerList() const { return myPlayers; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - ServerRules ServerQuery::serverRules() const { return myRules; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - QString ServerQuery::serverRuleValue(const QString &ruleName) const { ServerRule r; foreach(r, myRules) { if(r.rule == ruleName) return r.value; } return QString(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - char ServerQuery::ourReadableCharsTable[256] = { '.', '_' , '_' , '_' , '_' , '.' , '_' , '_' , '_' , '_' , '\n' , '_' , '\n' , '>' , '.' , '.', '[', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '.', '_', '_', '_' }; bool ServerQuery::ourReadableCharsTableInitialized = false; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void ServerQuery::fillReadableCharsTable() { int i; for(i = 32; i < 127; i++) ourReadableCharsTable[i] = ourReadableCharsTable[128 + i] = i; ourReadableCharsTable[127] = ourReadableCharsTable[128 + 127] = '_'; for(i = 0; i < 32; i++) ourReadableCharsTable[128 + i] = ourReadableCharsTable[i]; ourReadableCharsTable[128] = '_'; ourReadableCharsTable[10 + 128] = '_'; ourReadableCharsTable[12 + 128] = '_'; ourReadableCharsTableInitialized = true; } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - QString ServerQuery::convertNameFun(const QString &name) { if(!ourReadableCharsTableInitialized) fillReadableCharsTable(); QString stripped; for(int i = 0; i < name.length(); i++) stripped.append(QChar(ourReadableCharsTable[(unsigned char)name.at(i).toAscii()] & 127)); return stripped; }