diff --git a/CMakeLists.txt b/CMakeLists.txt index 1260b60ed..43c192b49 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,6 +69,7 @@ option(BUILD_OPENMW_MP "build OpenMW-MP" ON) option(BUILD_BSATOOL "build BSA extractor" ON) option(BUILD_ESMTOOL "build ESM inspector" ON) option(BUILD_LAUNCHER "build Launcher" ON) +option(BUILD_NETLAUNCHER "build Launcher" ON) option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON) option(BUILD_OPENCS "build OpenMW Construction Set" ON) @@ -128,7 +129,7 @@ endif() find_package(RakNet REQUIRED) include_directories(${RakNet_INCLUDES}) -if (NOT BUILD_LAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) +if (NOT BUILD_LAUNCHER AND NOT BUILD_NETLAUNCHER AND NOT BUILD_OPENCS AND NOT BUILD_WIZARD) set(USE_QT FALSE) else() set(USE_QT TRUE) @@ -393,6 +394,9 @@ IF(NOT WIN32 AND NOT APPLE) IF(BUILD_LAUNCHER) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-launcher" DESTINATION "${BINDIR}" ) ENDIF(BUILD_LAUNCHER) + IF(BUILD_NETLAUNCHER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-netlauncher" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_NETLAUNCHER) IF(BUILD_BSATOOL) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" ) ENDIF(BUILD_BSATOOL) @@ -493,6 +497,9 @@ if(WIN32) IF(BUILD_LAUNCHER) SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-launcher;OpenMW Launcher") ENDIF(BUILD_LAUNCHER) + IF(BUILD_NETLAUNCHER) + SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-netlauncher;tes3mp Launcher") + ENDIF(BUILD_NETLAUNCHER) IF(BUILD_OPENCS) SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-cs;OpenMW Construction Set") ENDIF(BUILD_OPENCS) @@ -579,6 +586,10 @@ if (BUILD_LAUNCHER) add_subdirectory( apps/launcher ) endif() +if (BUILD_NETLAUNCHER) + add_subdirectory( apps/netlauncher ) +endif() + if (BUILD_MWINIIMPORTER) add_subdirectory( apps/mwiniimporter ) endif() @@ -712,6 +723,10 @@ if (WIN32) set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() + if (BUILD_NETLAUNCHER) + set_target_properties(openmw-netlauncher PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + endif() + if (BUILD_MWINIIMPORTER) set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif() diff --git a/apps/netlauncher/CMakeLists.txt b/apps/netlauncher/CMakeLists.txt new file mode 100644 index 000000000..1fced10e2 --- /dev/null +++ b/apps/netlauncher/CMakeLists.txt @@ -0,0 +1,90 @@ +set(CMAKE_CXX_STANDARD 14) + +set(NETLAUNCHER_UI + ${CMAKE_SOURCE_DIR}/files/ui/Main.ui + ${CMAKE_SOURCE_DIR}/files/ui/ServerInfo.ui + ) +set(NETLAUNCHER + main.cpp + Main.cpp + ServerModel.cpp + NetController.cpp + ServerInfoDialog.cpp + netutils/HTTPNetwork.cpp + netutils/Utils.cpp + ${CMAKE_SOURCE_DIR}/files/windows/launcher.rc + ) + +set(NETLAUNCHER_HEADER_MOC + Main.hpp + ServerModel.hpp + ServerInfoDialog.hpp + ) + +set(NETLAUNCHER_HEADER + ${NETLAUNCHER_HEADER_MOC} + NetController.hpp + netutils/HTTPNetwork.hpp + netutils/Utils.hpp + ) + +source_group(netlauncher FILES ${NETLAUNCHER} ${NETLAUNCHER_HEADER}) + +set(QT_USE_QTGUI 1) + +# Set some platform specific settings +if(WIN32) + set(GUI_TYPE WIN32) + set(QT_USE_QTMAIN TRUE) +endif(WIN32) + +if (DESIRED_QT_VERSION MATCHES 4) + message(SEND_ERROR "QT4 is not supported.") + #include(${QT_USE_FILE}) + #QT4_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) + #QT4_WRAP_CPP(MOC_SRCS ${NETLAUNCHER_HEADER_MOC}) + #QT4_WRAP_UI(UI_HDRS ${NETLAUNCHER_UI}) +else() + QT5_ADD_RESOURCES(RCC_SRCS ${CMAKE_SOURCE_DIR}/files/launcher/launcher.qrc) + QT5_WRAP_CPP(MOC_SRCS ${NETLAUNCHER_HEADER_MOC}) + QT5_WRAP_UI(UI_HDRS ${NETLAUNCHER_UI}) +endif() + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) +if(NOT WIN32) + include_directories(${LIBUNSHIELD_INCLUDE_DIR}) +endif(NOT WIN32) + +# Main executable +add_executable(openmw-netlauncher + ${GUI_TYPE} + ${NETLAUNCHER} + ${NETLAUNCHER_HEADER} + ${RCC_SRCS} + ${MOC_SRCS} + ${UI_HDRS} + ) + +if (WIN32) + INSTALL(TARGETS openmw-netlauncher RUNTIME DESTINATION ".") +endif (WIN32) + +target_link_libraries(openmw-netlauncher + ${SDL2_LIBRARY_ONLY} + ${RakNet_LIBRARY} + components + ) + +if (DESIRED_QT_VERSION MATCHES 4) +# target_link_libraries(openmw-netlauncher ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY}) +# if(WIN32) +# target_link_libraries(openmw-netlauncher ${QT_QTMAIN_LIBRARY}) +# endif(WIN32) +else() + qt5_use_modules(openmw-netlauncher Widgets Core) +endif() + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions (--coverage) + target_link_libraries(openmw-netlauncher gcov) +endif() \ No newline at end of file diff --git a/apps/netlauncher/Main.cpp b/apps/netlauncher/Main.cpp new file mode 100644 index 000000000..1ea53ed4f --- /dev/null +++ b/apps/netlauncher/Main.cpp @@ -0,0 +1,137 @@ +// +// Created by koncord on 06.01.17. +// + +#include "Main.hpp" +#include "NetController.hpp" +#include "ServerInfoDialog.hpp" +#include +#include + +using namespace Process; + +Main::Main(QWidget *parent) +{ + setupUi(this); + + mGameInvoker = new ProcessInvoker(); + + browser = new ServerModel; + favorites = new ServerModel; + proxyModel = new QSortFilterProxyModel; + proxyModel->setSourceModel(browser); + tblServerBrowser->setModel(proxyModel); + tblFavorites->setModel(proxyModel); + + tblServerBrowser->hideColumn(ServerData::ADDR); + tblFavorites->hideColumn(ServerData::ADDR); + + refresh(); + + connect(tabWidget, SIGNAL(currentChanged(int)), this, SLOT(tabSwitched(int))); + connect(actionAdd, SIGNAL(triggered(bool)), this, SLOT(addServer())); + connect(actionAdd_by_IP, SIGNAL(triggered(bool)), this, SLOT(addServerByIP())); + connect(actionDelete, SIGNAL(triggered(bool)), this, SLOT(deleteServer())); + connect(actionRefresh, SIGNAL(triggered(bool)), this, SLOT(refresh())); + connect(actionPlay, SIGNAL(triggered(bool)), this, SLOT(play())); + connect(tblServerBrowser, SIGNAL(clicked(QModelIndex)), this, SLOT(serverSelected())); + connect(tblFavorites, SIGNAL(clicked(QModelIndex)), this, SLOT(serverSelected())); +} + +Main::~Main() +{ + delete mGameInvoker; +} + +void Main::addServer() +{ + int id = tblServerBrowser->selectionModel()->currentIndex().row(); + + if(id >= 0) + { + int sourceId = proxyModel->mapToSource(proxyModel->index(id, ServerData::ADDR)).row(); + favorites->myData.push_back(browser->myData[sourceId]); + } +} + +void Main::addServerByIP() +{ + bool ok; + QString text = QInputDialog::getText(this, tr("Add Server by address"), tr("Address:"), QLineEdit::Normal, "", &ok); + if(ok && !text.isEmpty()) + { + favorites->insertRows(0, 1); + QModelIndex mi = favorites->index(0, ServerData::ADDR); + favorites->setData(mi, text, Qt::EditRole); + NetController::get()->updateInfo(favorites, mi); + } +} + +void Main::deleteServer() +{ + if(tabWidget->currentIndex() != 1) + return; + int id = tblFavorites->selectionModel()->currentIndex().row(); + if(id >= 0) + { + int sourceId = proxyModel->mapToSource(proxyModel->index(id, ServerData::ADDR)).row(); + favorites->removeRow(sourceId); + if(favorites->myData.isEmpty()) + actionPlay->setEnabled(false); + } +} + +void Main::refresh() +{ + NetController::get()->updateInfo(proxyModel->sourceModel()); + /*tblServerBrowser->resizeColumnToContents(ServerData::HOSTNAME); + tblServerBrowser->resizeColumnToContents(ServerData::MODNAME); + tblFavorites->resizeColumnToContents(ServerData::HOSTNAME); + tblFavorites->resizeColumnToContents(ServerData::MODNAME);*/ +} + +void Main::play() +{ + QTableView *curTable = tabWidget->currentIndex() ? tblFavorites : tblServerBrowser; + int id = curTable->selectionModel()->currentIndex().row(); + if(id < 0) + return; + + ServerInfoDialog infoDialog(this); + ServerModel *sm = ((ServerModel*)proxyModel->sourceModel()); + + int sourceId = proxyModel->mapToSource(proxyModel->index(id, ServerData::ADDR)).row(); + NetController::get()->selectServer(&sm->myData[sourceId]); + infoDialog.refresh(); + if(!infoDialog.exec()) + return; + + QStringList arguments; + arguments.append(QLatin1String("--connect=") + sm->myData[sourceId].addr.toLatin1()); + + if (mGameInvoker->startProcess(QLatin1String("tes3mp"), arguments, true)) + return qApp->quit(); +} + +void Main::tabSwitched(int index) +{ + if(index == 0) + { + proxyModel->setSourceModel(browser); + actionDelete->setEnabled(false); + } + else + { + proxyModel->setSourceModel(favorites); + actionDelete->setEnabled(true); + } + actionPlay->setEnabled(false); + actionAdd->setEnabled(false); +} + +void Main::serverSelected() +{ + actionPlay->setEnabled(true); + if(tabWidget->currentIndex() == 0) + actionAdd->setEnabled(true); +} diff --git a/apps/netlauncher/Main.hpp b/apps/netlauncher/Main.hpp new file mode 100644 index 000000000..08c5d134e --- /dev/null +++ b/apps/netlauncher/Main.hpp @@ -0,0 +1,36 @@ +// +// Created by koncord on 06.01.17. +// + +#ifndef NEWLAUNCHER_MAIN_HPP +#define NEWLAUNCHER_MAIN_HPP + + +#include "ui_Main.h" +#include "ServerModel.hpp" +#include +#include + +class Main : public QMainWindow, private Ui::MainWindow +{ + Q_OBJECT +public: + explicit Main(QWidget *parent = 0); + virtual ~Main(); +protected: +protected slots: + void tabSwitched(int index); + void addServer(); + void addServerByIP(); + void deleteServer(); + void refresh(); + void play(); + void serverSelected(); +private: + Process::ProcessInvoker *mGameInvoker; + ServerModel *browser, *favorites; + QSortFilterProxyModel *proxyModel; +}; + + +#endif //NEWLAUNCHER_MAIN_HPP diff --git a/apps/netlauncher/NetController.cpp b/apps/netlauncher/NetController.cpp new file mode 100644 index 000000000..3c457a60e --- /dev/null +++ b/apps/netlauncher/NetController.cpp @@ -0,0 +1,215 @@ +// +// Created by koncord on 07.01.17. +// + +#include +#include +#include "NetController.hpp" +#include "qdebug.h" + +#include +#include + +#include + +#include +#include +#include + +using namespace std; + +NetController *NetController::mThis = nullptr; + +NetController *NetController::get() +{ + assert(mThis); + return mThis; +} + +void NetController::Create() +{ + assert(!mThis); + mThis = new NetController; +} + +void NetController::Destroy() +{ + assert(mThis); + delete mThis; + mThis = nullptr; +} + +NetController::NetController() : httpNetwork("127.0.0.1", 8080) +{ + +} + +NetController::~NetController() +{ + +} + +struct pattern +{ + pattern(QString value): value(value) {} + bool operator()(const ServerData &data) + { + return value == data.addr; + } + QString value; +}; + +void NetController::downloadInfo(QAbstractItemModel *pModel, QModelIndex index) +{ + ServerModel *model = ((ServerModel *) pModel); + + /* + * download stuff + */ + + QString data; + while (true) + { + data = QString::fromStdString(httpNetwork.getData("/api/servers")); + if (!data.isEmpty() && data != "NO_CONTENT" && data != "LOST_CONNECTION") + break; + RakSleep(30); + } + + qDebug() << "Content: " << data; + + QJsonParseError err; + QJsonDocument jsonDocument = QJsonDocument::fromJson(data.toLatin1(), &err); + + QMap map = jsonDocument.toVariant().toMap()["list servers"].toMap(); + + for(QMap::Iterator iter = map.begin(); iter != map.end(); iter++) + { + qDebug() << iter.key(); + qDebug() << iter.value().toMap()["hostname"].toString(); + qDebug() << iter.value().toMap()["modname"].toString(); + qDebug() << iter.value().toMap()["players"].toInt(); + qDebug() << iter.value().toMap()["max_players"].toInt(); + + QVector::Iterator value = std::find_if(model->myData.begin(), model->myData.end(), pattern(iter.key())); + if(value == model->myData.end()) + model->insertRow(0); + + QModelIndex mi = model->index(0, ServerData::ADDR); + model->setData(mi, iter.key()); + + mi = model->index(0, ServerData::PLAYERS); + model->setData(mi, iter.value().toMap()["players"].toInt()); + + mi = model->index(0, ServerData::MAX_PLAYERS); + model->setData(mi, iter.value().toMap()["max_players"].toInt()); + + mi = model->index(0, ServerData::HOSTNAME); + model->setData(mi, iter.value().toMap()["hostname"].toString()); + + mi = model->index(0, ServerData::MODNAME); + model->setData(mi, iter.value().toMap()["modname"].toString()); + + mi = model->index(0, ServerData::PING); + + QStringList addr = iter.key().split(":"); + model->setData(mi, PingRakNetServer(addr[0].toLatin1().data(), addr[1].toUShort())); + } + + //qDebug() << data; + + if (model->rowCount() != 0) + return; + + /*model->insertRows(0, 6); + model->myData[0] = {"127.0.0.1:25565", 0, 20, 1, "Super Server"}; + model->myData[1] = {"127.0.0.1:25565", 5, 20, 2, "Koncord's server"}; + model->myData[2] = {"server.local:8888", 15, 15, 15, "tes3mp server", "custom mode"}; + model->myData[3] = {"127.0.0.1:25562", 1, 2, 5, "Server"}; + model->myData[4] = {"tes3mp.com:22222", 8, 9, 1000, "Antoher Server", "super duper mod"}; + model->myData[5] = {"localhost:24", 1, 5, 5, "Test Server", "Another mod 0.1"};*/ +} + +void NetController::updateInfo(QAbstractItemModel *pModel, QModelIndex index) +{ + qDebug() << "TODO: \"NetController::updateInfo(QAbstractItemModel *, QModelIndex)\" is not completed"; + ServerModel *model = ((ServerModel*)pModel); + + if (index.isValid() && index.row() >= 0) + { + //ServerData &sd = model->myData[index.row()]; + //qDebug() << sd.addr; + downloadInfo(pModel, index); + } + else + { + for (QVector::Iterator iter = model->myData.begin(); iter != model->myData.end(); iter++) + { + qDebug() << iter->addr; + } + model->removeRows(0, model->rowCount(index)); + downloadInfo(pModel, index); + } +} + +void NetController::updateInfo() +{ + QString data; + QString uri = "/api/servers/" + sd->addr; + while (true) + { + data = QString::fromStdString(httpNetwork.getData(uri.toLatin1())); + if (!data.isEmpty() && data != "NO_CONTENT" && data != "LOST_CONNECTION") + break; + RakSleep(30); + } + + qDebug() << "Content: " << data; + + QJsonParseError err; + QJsonDocument jsonDocument = QJsonDocument::fromJson(data.toLatin1(), &err); + + QMap map = jsonDocument.toVariant().toMap()["server"].toMap(); + + qDebug() << sd->addr; + qDebug() << map["hostname"].toString(); + qDebug() << map["modname"].toString(); + qDebug() << map["players"].toInt(); + qDebug() << map["max_players"].toInt(); + + sd->hostName = map["hostname"].toString(); + sd->modName = map["modname"].toString(); + sd->players = map["players"].toInt(); + sd->maxPlayers = map["max_players"].toInt(); + + QStringList addr = sd->addr.split(":"); + sd->ping = PingRakNetServer(addr[0].toLatin1(), addr[1].toUShort()); + sed = getExtendedData(addr[0].toLatin1(), addr[1].toUShort()); +} + +QStringList NetController::players() +{ + QStringList listPlayers; + for(vector::iterator player = sed.players.begin(); player != sed.players.end(); player++) + listPlayers.push_back(player->c_str()); + return listPlayers; +} + +QStringList NetController::plugins() +{ + QStringList listPlugins; + for(vector::iterator plugin = sed.plugins.begin(); plugin != sed.plugins.end(); plugin++) + listPlugins.push_back(plugin->c_str()); + return listPlugins; +} + +void NetController::selectServer(ServerData *pServerData) +{ + sd = pServerData; +} + +ServerData *NetController::selectedServer() +{ + return sd; +} + diff --git a/apps/netlauncher/NetController.hpp b/apps/netlauncher/NetController.hpp new file mode 100644 index 000000000..846ea0986 --- /dev/null +++ b/apps/netlauncher/NetController.hpp @@ -0,0 +1,41 @@ +// +// Created by koncord on 07.01.17. +// + +#ifndef NEWLAUNCHER_NETCONTROLLER_HPP +#define NEWLAUNCHER_NETCONTROLLER_HPP + + +#include "ServerModel.hpp" +#include "netutils/HTTPNetwork.hpp" +#include "netutils/Utils.hpp" + +struct ServerModel; + +class NetController +{ +public: + static NetController *get(); + static void Create(); + static void Destroy(); + void updateInfo(QAbstractItemModel *pModel, QModelIndex index= QModelIndex()); + void updateInfo(); + QStringList players(); + QStringList plugins(); + void selectServer(ServerData *pServerData); + ServerData *selectedServer(); +protected: + NetController(); + ~NetController(); +private: + NetController(const NetController &controller); + void downloadInfo(QAbstractItemModel *pModel, QModelIndex index); + + static NetController *mThis; + ServerData *sd; + HTTPNetwork httpNetwork; + ServerExtendedData sed; +}; + + +#endif //NEWLAUNCHER_NETCONTROLLER_HPP diff --git a/apps/netlauncher/ServerInfoDialog.cpp b/apps/netlauncher/ServerInfoDialog.cpp new file mode 100644 index 000000000..2fe75b890 --- /dev/null +++ b/apps/netlauncher/ServerInfoDialog.cpp @@ -0,0 +1,36 @@ +// +// Created by koncord on 07.01.17. +// + +#include "qdebug.h" +#include "NetController.hpp" + +#include "ServerInfoDialog.hpp" + +ServerInfoDialog::ServerInfoDialog(QWidget *parent): QDialog(parent) +{ + setupUi(this); + connect(btnRefresh, SIGNAL(clicked()), this, SLOT(refresh())); +} + +ServerInfoDialog::~ServerInfoDialog() +{ + +} + +void ServerInfoDialog::refresh() +{ + NetController::get()->updateInfo(); + ServerData *sd = NetController::get()->selectedServer(); + leAddr->setText(sd->addr); + lblName->setText(sd->hostName); + lblPing->setNum(sd->ping); + + listPlayers->clear(); + QStringList players = NetController::get()->players(); + listPlayers->addItems(players); + listPlugins->clear(); + listPlugins->addItems(NetController::get()->plugins()); + + lblPlayers->setText(QString::number(players.size()) + " / " + QString::number(sd->maxPlayers)); +} diff --git a/apps/netlauncher/ServerInfoDialog.hpp b/apps/netlauncher/ServerInfoDialog.hpp new file mode 100644 index 000000000..b26b34df5 --- /dev/null +++ b/apps/netlauncher/ServerInfoDialog.hpp @@ -0,0 +1,21 @@ +// +// Created by koncord on 07.01.17. +// + +#ifndef NEWLAUNCHER_SERVERINFODIALOG_HPP +#define NEWLAUNCHER_SERVERINFODIALOG_HPP + +#include "ui_ServerInfo.h" + +class ServerInfoDialog : public QDialog, public Ui::Dialog +{ + Q_OBJECT +public: + explicit ServerInfoDialog(QWidget *parent = 0); + virtual ~ServerInfoDialog(); +public slots: + void refresh(); +}; + + +#endif //NEWLAUNCHER_SERVERINFODIALOG_HPP diff --git a/apps/netlauncher/ServerModel.cpp b/apps/netlauncher/ServerModel.cpp new file mode 100644 index 000000000..4c82f5d8a --- /dev/null +++ b/apps/netlauncher/ServerModel.cpp @@ -0,0 +1,178 @@ +#include +#include "ServerModel.hpp" +#include + +ServerModel::ServerModel(QObject *parent) : QAbstractTableModel(parent) +{ +} + +ServerModel::~ServerModel() +{ + +} + +/*QHash ServerModel::roleNames() const +{ + return roles; +}*/ + +QVariant ServerModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + if (index.row() < 0 || index.row() > myData.size()) + return QVariant(); + + const ServerData &sd = myData.at(index.row()); + + if(role == Qt::DisplayRole) + { + QVariant var; + switch (index.column()) + { + case ServerData::ADDR: + var = sd.addr; + break; + case ServerData::PLAYERS: + var = sd.players; + break; + case ServerData::MAX_PLAYERS: + var = sd.maxPlayers; + break; + case ServerData::HOSTNAME: + var = sd.hostName; + break; + case ServerData::PING: + var = sd.ping; + break; + case ServerData::MODNAME: + if(sd.modName.isEmpty()) + var = "default"; + else + var = sd.modName; + break; + } + return var; + } + return QVariant(); +} + +QVariant ServerModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + QVariant var; + if (orientation == Qt::Horizontal) + { + if (role == Qt::SizeHintRole) + { + /*if(section == ServerData::HOSTNAME) + var = QSize(200, 25);*/ + } + else if (role == Qt::DisplayRole) + { + + switch (section) + { + case ServerData::ADDR: + var = "Address"; + break; + case ServerData::HOSTNAME: + var = "Host name"; + break; + case ServerData::PLAYERS: + var = "Players"; + break; + case ServerData::MAX_PLAYERS: + var = "Player Max"; + break; + case ServerData::PING: + var = "Ping"; + break; + case ServerData::MODNAME: + var = "Game mode"; + } + } + } + return var; +} + +int ServerModel::rowCount(const QModelIndex &parent) const +{ + return myData.size(); +} + +int ServerModel::columnCount(const QModelIndex &parent) const +{ + return ServerData::LAST; +} + +bool ServerModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (index.isValid() && role == Qt::EditRole) + { + int row = index.row(); + int col = index.column(); + + ServerData &sd = myData[row]; + bool ok = true; + switch(col) + { + case ServerData::ADDR: + sd.addr = value.toString(); + ok = !sd.addr.isEmpty(); + break; + case ServerData::PLAYERS: + sd.players = value.toInt(&ok); + break; + case ServerData::MAX_PLAYERS: + sd.maxPlayers = value.toInt(&ok); + break; + case ServerData::HOSTNAME: + sd.hostName = value.toString(); + ok = !sd.addr.isEmpty(); + break; + case ServerData::PING: + sd.ping = value.toInt(&ok); + break; + case ServerData::MODNAME: + sd.modName = value.toString(); + break; + default: + return false; + } + if(ok) + emit(dataChanged(index, index)); + return true; + } + return false; +} + +bool ServerModel::insertRows(int position, int count, const QModelIndex &index) +{ + Q_UNUSED(index); + beginInsertRows(QModelIndex(), position, position + count - 1); + + for (int row = 0; row < count; ++row) { + ServerData sd {"", -1, -1, -1, ""}; + myData.insert(position, sd); + } + + endInsertRows(); + return true; +} + +bool ServerModel::removeRows(int position, int count, const QModelIndex &parent) +{ + beginRemoveRows(parent, position, position + count - 1); + myData.erase(myData.begin()+position, myData.begin() + position + count); + endRemoveRows(); + + return true; +} + +QModelIndex ServerModel::index(int row, int column, const QModelIndex &parent) const +{ + + QModelIndex index = QAbstractTableModel::index(row, column, parent); + //qDebug() << "Valid index? " << index.isValid() << " " << row << " " << column; + return index; +} diff --git a/apps/netlauncher/ServerModel.hpp b/apps/netlauncher/ServerModel.hpp new file mode 100644 index 000000000..3acd7ac66 --- /dev/null +++ b/apps/netlauncher/ServerModel.hpp @@ -0,0 +1,52 @@ +#ifndef SERVERMODEL_FONTMODEL_HPP +#define SERVERMODEL_FONTMODEL_HPP + +#include +#include +#include +#include + +struct ServerData +{ + QString addr; + int players, maxPlayers; + int ping; + QString hostName; + QString modName; + enum IDS + { + ADDR, + HOSTNAME, + PLAYERS, + MAX_PLAYERS, + MODNAME, + PING, + LAST + }; +}; + +class ServerModel: public QAbstractTableModel +{ + Q_OBJECT +public: + explicit ServerModel(QObject *parent = 0); + ~ServerModel(); + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent) const; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); + + bool insertRows(int row, int count, const QModelIndex &index = QModelIndex()); + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; + + +public: + //QHash roles; + QVector myData; +}; + + +#endif //SERVERMODEL_FONTMODEL_HPP diff --git a/apps/netlauncher/main.cpp b/apps/netlauncher/main.cpp new file mode 100644 index 000000000..81b5a3270 --- /dev/null +++ b/apps/netlauncher/main.cpp @@ -0,0 +1,17 @@ +#include +#include "Main.hpp" +#include "NetController.hpp" + +int main(int argc, char *argv[]) +{ + // initialize resources, if needed + // Q_INIT_RESOURCE(resfile); + + NetController::Create(); + atexit(NetController::Destroy); + QApplication app(argc, argv); + Main d; + d.show(); + // create and show your widgets here + return app.exec(); +} \ No newline at end of file diff --git a/apps/netlauncher/netutils/HTTPNetwork.cpp b/apps/netlauncher/netutils/HTTPNetwork.cpp new file mode 100644 index 000000000..c59b1f52a --- /dev/null +++ b/apps/netlauncher/netutils/HTTPNetwork.cpp @@ -0,0 +1,96 @@ +// +// Created by koncord on 07.01.17. +// + +#include +#include +#include +#include + +#include + +#include "HTTPNetwork.hpp" + +using namespace RakNet; + +HTTPNetwork::HTTPNetwork(std::string addr, unsigned short port) : address(addr), port(port) +{ + httpConnection = HTTPConnection2::GetInstance(); + tcpInterface = new TCPInterface; + tcpInterface->Start(0, 64); + tcpInterface->AttachPlugin(httpConnection); +} + +HTTPNetwork::~HTTPNetwork() +{ + delete tcpInterface; +} + +std::string HTTPNetwork::answer() +{ + RakNet::SystemAddress sa; + RakNet::Packet *packet; + RakNet::SystemAddress hostReceived; + RakNet::RakString response; + RakNet::RakString transmitted, hostTransmitted; + int contentOffset = 0; + + while (true) + { + // This is kind of crappy, but for TCP plugins, always do HasCompletedConnectionAttempt, + // then Receive(), then HasFailedConnectionAttempt(),HasLostConnection() + sa = tcpInterface->HasCompletedConnectionAttempt(); + if (sa != RakNet::UNASSIGNED_SYSTEM_ADDRESS) + printf("Connected to master server: %s\n", sa.ToString()); + + sa = tcpInterface->HasFailedConnectionAttempt(); + if (sa != RakNet::UNASSIGNED_SYSTEM_ADDRESS) + { + printf("Failed to connect to master server: %s\n", sa.ToString()); + return "FAIL_CONNECT"; + } + sa = tcpInterface->HasLostConnection(); + if (sa != RakNet::UNASSIGNED_SYSTEM_ADDRESS) + { + printf("Lost connection to master server: %s\n", sa.ToString()); + return "LOST_CONNECTION"; + } + + for (packet = tcpInterface->Receive(); packet; tcpInterface->DeallocatePacket( + packet), packet = tcpInterface->Receive()); + + if (httpConnection->GetResponse(transmitted, hostTransmitted, response, hostReceived, contentOffset)) + { + if (contentOffset < 0) + return "NO_CONTENT"; // no content + tcpInterface->CloseConnection(sa); + + return (response.C_String() + contentOffset); + } + RakSleep(30); + } +} + +std::string HTTPNetwork::getData(const char *uri) +{ + RakNet::RakString createRequest = RakNet::RakString::FormatForGET(uri); + + httpConnection->TransmitRequest(createRequest, address.c_str(), port); + return answer(); +} + +std::string HTTPNetwork::getDataPOST(const char *uri, const char* body, const char* contentType) +{ + RakNet::RakString createRequest = RakNet::RakString::FormatForPOST(uri, contentType, body); + + httpConnection->TransmitRequest(createRequest, address.c_str(), port); + return answer(); +} + +std::string HTTPNetwork::getDataPUT(const char *uri, const char* body, const char* contentType) +{ + RakNet::RakString createRequest = RakNet::RakString::FormatForPUT(uri, contentType, body); + + httpConnection->TransmitRequest(createRequest, address.c_str(), port); + return answer(); +} diff --git a/apps/netlauncher/netutils/HTTPNetwork.hpp b/apps/netlauncher/netutils/HTTPNetwork.hpp new file mode 100644 index 000000000..8597ddcb0 --- /dev/null +++ b/apps/netlauncher/netutils/HTTPNetwork.hpp @@ -0,0 +1,35 @@ +// +// Created by koncord on 07.01.17. +// + +#ifndef NEWLAUNCHER_HTTPNETWORK_HPP +#define NEWLAUNCHER_HTTPNETWORK_HPP + + +#include + +namespace RakNet +{ + class TCPInterface; + class HTTPConnection2; +} + +class HTTPNetwork +{ +public: + HTTPNetwork(std::string addr, unsigned short port); + ~HTTPNetwork(); + std::string getData(const char *uri); + std::string getDataPOST(const char *uri, const char* body, const char* contentType = "application/json"); + std::string getDataPUT(const char *uri, const char* body, const char* contentType = "application/json"); + +protected: + RakNet::TCPInterface *tcpInterface; + RakNet::HTTPConnection2 *httpConnection; + std::string address; + unsigned short port; + std::string answer(); +}; + + +#endif //NEWLAUNCHER_HTTPNETWORK_HPP diff --git a/apps/netlauncher/netutils/Utils.cpp b/apps/netlauncher/netutils/Utils.cpp new file mode 100644 index 000000000..4379d0f50 --- /dev/null +++ b/apps/netlauncher/netutils/Utils.cpp @@ -0,0 +1,151 @@ +// +// Created by koncord on 07.01.17. +// + +#include +#include +#include +#include + +#include +#include +#include + +#include "Utils.hpp" + +using namespace std; + +unsigned int PingRakNetServer(const char *addr, unsigned short port) +{ + RakNet::Packet *packet; + bool done = false; + int attempts = 0; + RakNet::TimeMS time = 999; + + RakNet::SocketDescriptor socketDescriptor = {0, ""}; + RakNet::RakPeerInterface *peer = RakNet::RakPeerInterface::GetInstance(); + peer->Startup(1,&socketDescriptor, 1); + + peer->Ping(addr, port, false); + while (!done) + { + for (packet=peer->Receive(); packet; peer->DeallocatePacket(packet), packet=peer->Receive()) + { + if(packet->data[0] == ID_UNCONNECTED_PONG) + { + RakNet::BitStream bsIn(&packet->data[1], packet->length, false); + bsIn.Read(time); + time = RakNet::GetTimeMS() - time - 5; + done = true; + break; + } + } + + if (attempts >= 60) // wait 300 msec + done = true; + attempts++; + RakSleep(5); + } + RakNet::RakPeerInterface::DestroyInstance(peer); + return time; +} + +ServerExtendedData getExtendedData(const char *addr, unsigned short port) +{ + ServerExtendedData data; + RakNet::SocketDescriptor socketDescriptor = {0, ""}; + RakNet::RakPeerInterface *peer = RakNet::RakPeerInterface::GetInstance(); + peer->Startup(1, &socketDescriptor, 1); + + stringstream sstr(TES3MP_VERSION); + sstr << TES3MP_PROTO_VERSION; + + std::string msg = ""; + + if (peer->Connect(addr, port, sstr.str().c_str(), (int)(sstr.str().size()), 0, 0, 3, 500, 0) != RakNet::CONNECTION_ATTEMPT_STARTED) + msg = "Connection attempt failed.\n"; + + + bool queue = true; + while (queue) + { + for (RakNet::Packet *packet = peer->Receive(); packet; peer->DeallocatePacket( + packet), packet = peer->Receive()) + { + switch (packet->data[0]) + { + case ID_CONNECTION_ATTEMPT_FAILED: + { + msg = "Connection failed.\n" + "Either the IP address is wrong or a firewall on either system is blocking\n" + "UDP packets on the port you have chosen."; + queue = false; + break; + } + case ID_INVALID_PASSWORD: + { + msg = "Connection failed.\n" + "The client or server is outdated.\n"; + queue = false; + break; + } + case ID_CONNECTION_REQUEST_ACCEPTED: + { + msg = "Connection accepted.\n"; + queue = false; + break; + } + case ID_DISCONNECTION_NOTIFICATION: + throw runtime_error("ID_DISCONNECTION_NOTIFICATION.\n"); + case ID_CONNECTION_BANNED: + throw runtime_error("ID_CONNECTION_BANNED.\n"); + case ID_CONNECTION_LOST: + throw runtime_error("ID_CONNECTION_LOST.\n"); + default: + printf("Connection message with identifier %i has arrived in initialization.\n", packet->data[0]); + } + } + } + puts(msg.c_str()); + + { + RakNet::BitStream bs; + bs.Write((unsigned char) (ID_USER_PACKET_ENUM + 1)); + peer->Send(&bs, HIGH_PRIORITY, RELIABLE_ORDERED, 0, RakNet::UNASSIGNED_SYSTEM_ADDRESS, true); + } + + RakNet::Packet *packet; + bool done = false; + while (!done) + { + for (packet = peer->Receive(); packet; peer->DeallocatePacket(packet), packet = peer->Receive()) + { + if(packet->data[0] == (ID_USER_PACKET_ENUM+1)) + { + RakNet::BitStream bs(packet->data, packet->length, false); + bs.IgnoreBytes(1); + size_t length = 0; + bs.Read(length); + for(int i = 0; i < length; i++) + { + RakNet::RakString str; + bs.Read(str); + data.players.push_back(str.C_String()); + } + bs.Read(length); + for(int i = 0; i < length; i++) + { + RakNet::RakString str; + bs.Read(str); + data.plugins.push_back(str.C_String()); + } + done = true; + } + } + } + + peer->Shutdown(1); + RakSleep(10); + RakNet::RakPeerInterface::DestroyInstance(peer); + return data; +} diff --git a/apps/netlauncher/netutils/Utils.hpp b/apps/netlauncher/netutils/Utils.hpp new file mode 100644 index 000000000..99e3e49a6 --- /dev/null +++ b/apps/netlauncher/netutils/Utils.hpp @@ -0,0 +1,22 @@ +// +// Created by koncord on 07.01.17. +// + +#ifndef NEWLAUNCHER_PING_HPP +#define NEWLAUNCHER_PING_HPP + +#include +#include + + +unsigned int PingRakNetServer(const char *addr, unsigned short port); + +struct ServerExtendedData +{ + std::vector players; + std::vector plugins; +}; + +ServerExtendedData getExtendedData(const char *addr, unsigned short port); + +#endif //NEWLAUNCHER_PING_HPP diff --git a/files/ui/Main.ui b/files/ui/Main.ui new file mode 100644 index 000000000..e05dbc51e --- /dev/null +++ b/files/ui/Main.ui @@ -0,0 +1,134 @@ + + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + MainWindow + + + + + + + 0 + + + + Browser + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + + + + + + Favorites + + + + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + + + + + + + + + + true + + + toolBar + + + false + + + false + + + TopToolBarArea + + + false + + + + + + + + + + + + false + + + Add + + + + + false + + + Delete + + + + + Refresh + + + + + Play + + + + + Add by address + + + + + + diff --git a/files/ui/ServerInfo.ui b/files/ui/ServerInfo.ui new file mode 100644 index 000000000..3ecae318b --- /dev/null +++ b/files/ui/ServerInfo.ui @@ -0,0 +1,240 @@ + + + Dialog + + + + 0 + 0 + 430 + 447 + + + + Connect + + + + + + + + Server Name: + + + + + + + TextLabel + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Address: + + + + + + + + + + true + + + + + + + + + + + Players: + + + + + + + 0 / 0 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Ping: + + + + + + + -1 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Players: + + + + + + + QAbstractItemView::NoSelection + + + + + + + Plugins: + + + + + + + QAbstractItemView::NoSelection + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + false + + + + + + + Refresh + + + + + + + Connect + + + true + + + + + + + + + + + btnCancel + clicked() + Dialog + reject() + + + 489 + 524 + + + 316 + 274 + + + + + btnConnect + clicked() + Dialog + accept() + + + 580 + 524 + + + 316 + 274 + + + + +