diff --git a/Attorney_Online.pro b/Attorney_Online.pro index 1fb5d0b..95e3945 100644 --- a/Attorney_Online.pro +++ b/Attorney_Online.pro @@ -1,4 +1,4 @@ -QT += core gui widgets network +QT += core gui widgets network websockets TARGET = Attorney_Online TEMPLATE = app diff --git a/CMakeLists.txt b/CMakeLists.txt index 12a63c3..9d7714b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -31,10 +31,10 @@ endif() target_include_directories(Attorney_Online PRIVATE include) # Target Lib -find_package(Qt5 COMPONENTS Core Gui Network Widgets Concurrent REQUIRED) +find_package(Qt5 COMPONENTS Core Gui Network Widgets Concurrent WebSockets REQUIRED) target_link_directories(Attorney_Online PRIVATE lib) target_link_libraries(Attorney_Online PRIVATE Qt5::Core Qt5::Gui Qt5::Network Qt5::Widgets Qt5::Concurrent - bass bassmidi bassopus discord-rpc) + Qt5::WebSockets bass bassmidi bassopus discord-rpc) target_compile_definitions(Attorney_Online PRIVATE DISCORD) # Subdirectories diff --git a/base/favorite_servers_sample.ini b/base/favorite_servers_sample.ini new file mode 100644 index 0000000..10126d2 --- /dev/null +++ b/base/favorite_servers_sample.ini @@ -0,0 +1,5 @@ +[0] +name=Default Local Server +address=127.0.0.1 +port=27016 +protocol=tcp \ No newline at end of file diff --git a/base/serverlist.sample.txt b/base/serverlist.sample.txt deleted file mode 100644 index f700836..0000000 --- a/base/serverlist.sample.txt +++ /dev/null @@ -1 +0,0 @@ -127.0.0.1:27016:Default local server diff --git a/include/aoapplication.h b/include/aoapplication.h index 4809c3f..4c3a83f 100644 --- a/include/aoapplication.h +++ b/include/aoapplication.h @@ -133,6 +133,8 @@ public: void set_favorite_list(); QVector &get_favorite_list() { return favorite_list; } + + // Adds the server to favorite_servers.ini void add_favorite_server(int p_server); void set_server_list(QVector &servers) { server_list = servers; } @@ -324,12 +326,14 @@ public: // Append to the currently open demo file if there is one void append_to_demofile(QString packet_string); - // Appends the argument string to serverlist.txt - void write_to_serverlist_txt(QString p_line); - // Returns the contents of serverlist.txt QVector read_serverlist_txt(); + /** + * @brief Migrates the favorite serverlist format from txt to ini. + */ + void migrate_serverlist_txt(QFile &p_serverlist_txt); + // Returns the value of p_identifier in the design.ini file in p_design_path QString read_design_ini(QString p_identifier, VPath p_design_path); QString read_design_ini(QString p_identifier, QString p_design_path); diff --git a/include/datatypes.h b/include/datatypes.h index 2f03d39..3289bd8 100644 --- a/include/datatypes.h +++ b/include/datatypes.h @@ -1,13 +1,25 @@ #ifndef DATATYPES_H #define DATATYPES_H +#include #include +enum connection_type { + TCP, + WEBSOCKETS, +}; + +static QMap to_connection_type = { + {"tcp", connection_type::TCP}, + {"ws", connection_type::WEBSOCKETS} +}; + struct server_type { QString name; QString desc; QString ip; int port; + connection_type socket_type; }; struct emote_type { diff --git a/include/networkmanager.h b/include/networkmanager.h index 26a61f2..705fb9e 100644 --- a/include/networkmanager.h +++ b/include/networkmanager.h @@ -6,7 +6,7 @@ #include #include -#include +#include #include #include @@ -21,16 +21,20 @@ enum MSDocumentType { class NetworkManager : public QObject { Q_OBJECT -public: - explicit NetworkManager(AOApplication *parent); - ~NetworkManager() = default; - +private: AOApplication *ao_app; QNetworkAccessManager *http; - QTcpSocket *server_socket; + + union { + QWebSocket *ws; + QTcpSocket *tcp; + } server_socket; + connection_type active_connection_type; + bool connected = false; + QTimer *heartbeat_timer; - const QString DEFAULT_MS_BASEURL = "https://servers.aceattorneyonline.com"; + const QString DEFAULT_MS_BASEURL = "http://servers.aceattorneyonline.com"; QString ms_baseurl = DEFAULT_MS_BASEURL; const int heartbeat_interval = 60 * 5 * 1000; @@ -40,12 +44,17 @@ public: unsigned int s_decryptor = 5; +public: + explicit NetworkManager(AOApplication *parent); + ~NetworkManager() = default; + void connect_to_server(server_type p_server); + void disconnect_from_server(); public slots: void get_server_list(const std::function &cb); void ship_server_packet(QString p_packet); - void handle_server_packet(); + void handle_server_packet(const QString& p_data); void request_document(MSDocumentType document_type, const std::function &cb); diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp index 13c995f..1e70ca9 100644 --- a/src/aoapplication.cpp +++ b/src/aoapplication.cpp @@ -144,12 +144,22 @@ void AOApplication::add_favorite_server(int p_server) return; server_type fav_server = server_list.at(p_server); + QSettings l_favorite_ini(get_base_path() + "favorite_servers.ini", QSettings::IniFormat); + QString l_new_group = QString::number(l_favorite_ini.childGroups().size()); + l_favorite_ini.setIniCodec("UTF-8"); - QString str_port = QString::number(fav_server.port); + l_favorite_ini.beginGroup(l_new_group); + l_favorite_ini.setValue("name", fav_server.name); + l_favorite_ini.setValue("address", fav_server.ip); + l_favorite_ini.setValue("port", fav_server.port); - QString server_line = fav_server.ip + ":" + str_port + ":" + fav_server.name; - - write_to_serverlist_txt(server_line); + if (fav_server.socket_type == TCP) { + l_favorite_ini.setValue("protocol", "tcp"); + } + else { + l_favorite_ini.setValue("protocol", "ws"); + } + l_favorite_ini.sync(); } void AOApplication::server_disconnected() diff --git a/src/networkmanager.cpp b/src/networkmanager.cpp index 4c82385..8f419fc 100644 --- a/src/networkmanager.cpp +++ b/src/networkmanager.cpp @@ -4,6 +4,7 @@ #include "debug_functions.h" #include "lobby.h" +#include #include #include #include @@ -12,15 +13,9 @@ NetworkManager::NetworkManager(AOApplication *parent) : QObject(parent) { ao_app = parent; - server_socket = new QTcpSocket(this); http = new QNetworkAccessManager(this); heartbeat_timer = new QTimer(this); - connect(server_socket, &QTcpSocket::readyRead, this, - &NetworkManager::handle_server_packet); - connect(server_socket, &QTcpSocket::disconnected, ao_app, - &AOApplication::server_disconnected); - QString master_config = ao_app->configini->value("master", "").value(); if (!master_config.isEmpty() && QUrl(master_config).scheme().startsWith("http")) { @@ -60,10 +55,18 @@ void NetworkManager::ms_request_finished(QNetworkReply *reply, const auto entry = entryRef.toObject(); server_type server; server.ip = entry["ip"].toString(); - server.port = entry["port"].toInt(); server.name = entry["name"].toString(); server.desc = entry["description"].toString(tr("No description provided.")); - server_list.append(server); + if (entry["ws_port"].isDouble()) { + server.socket_type = WEBSOCKETS; + server.port = entry["ws_port"].toInt(); + } else { + server.socket_type = TCP; + server.port = entry["port"].toInt(); + } + if (server.port != 0) { + server_list.append(server); + } } ao_app->set_server_list(server_list); @@ -128,26 +131,99 @@ void NetworkManager::request_document(MSDocumentType document_type, void NetworkManager::connect_to_server(server_type p_server) { - server_socket->close(); - server_socket->abort(); + disconnect_from_server(); qInfo().nospace().noquote() << "connecting to " << p_server.ip << ":" << p_server.port; - server_socket->connectToHost(p_server.ip, p_server.port); + switch (p_server.socket_type) { + default: + p_server.socket_type = TCP; + [[fallthrough]]; + case TCP: + qInfo() << "using TCP backend"; + server_socket.tcp = new QTcpSocket(this); + + connect(server_socket.tcp, &QAbstractSocket::connected, this, [] { + qDebug() << "established connection to server"; + }); + connect(server_socket.tcp, &QIODevice::readyRead, this, [this] { + handle_server_packet(QString::fromUtf8(server_socket.tcp->readAll())); + }); + connect(server_socket.tcp, &QAbstractSocket::disconnected, ao_app, + &AOApplication::server_disconnected); + connect(server_socket.tcp, &QAbstractSocket::errorOccurred, this, [this] { + qCritical() << "TCP socket error:" << server_socket.tcp->errorString(); + }); + + server_socket.tcp->connectToHost(p_server.ip, p_server.port); + break; + case WEBSOCKETS: + qInfo() << "using WebSockets backend"; + server_socket.ws = new QWebSocket(QString(), QWebSocketProtocol::VersionLatest, this); + + connect(server_socket.ws, &QWebSocket::connected, this, [] { + qDebug() << "established connection to server"; + }); + connect(server_socket.ws, &QWebSocket::textMessageReceived, this, + &NetworkManager::handle_server_packet); + connect(server_socket.ws, &QWebSocket::disconnected, ao_app, + &AOApplication::server_disconnected); + connect(server_socket.ws, QOverload::of(&QWebSocket::error), + this, [this] { + qCritical() << "WebSockets error:" << server_socket.ws->errorString(); + }); + + QUrl url; + url.setScheme("ws"); + url.setHost(p_server.ip); + url.setPort(p_server.port); + QNetworkRequest req(url); + req.setHeader(QNetworkRequest::UserAgentHeader, get_user_agent()); + server_socket.ws->open(req); + break; + } + + connected = true; + active_connection_type = p_server.socket_type; +} + +void NetworkManager::disconnect_from_server() +{ + if (!connected) + return; + + switch (active_connection_type) { + case TCP: + server_socket.tcp->close(); + server_socket.tcp->deleteLater(); + break; + case WEBSOCKETS: + server_socket.ws->close(QWebSocketProtocol::CloseCodeGoingAway); + server_socket.ws->deleteLater(); + break; + } + + connected = false; } void NetworkManager::ship_server_packet(QString p_packet) { - server_socket->write(p_packet.toUtf8()); + switch (active_connection_type) { + case TCP: + server_socket.tcp->write(p_packet.toUtf8()); + break; + case WEBSOCKETS: + server_socket.ws->sendTextMessage(p_packet); + break; + } } -void NetworkManager::handle_server_packet() +void NetworkManager::handle_server_packet(const QString& p_data) { - QByteArray buffer = server_socket->readAll(); - QString in_data = QString::fromUtf8(buffer, buffer.size()); + QString in_data = p_data; - if (!in_data.endsWith("%")) { + if (!p_data.endsWith("%")) { partial_packet = true; temp_packet += in_data; return; diff --git a/src/text_file_functions.cpp b/src/text_file_functions.cpp index b5bd696..0dde68f 100644 --- a/src/text_file_functions.cpp +++ b/src/text_file_functions.cpp @@ -236,52 +236,30 @@ bool AOApplication::append_to_file(QString p_text, QString p_file, return false; } -void AOApplication::write_to_serverlist_txt(QString p_line) -{ - QFile serverlist_txt; - QString serverlist_txt_path = get_base_path() + "serverlist.txt"; - - serverlist_txt.setFileName(serverlist_txt_path); - - if (!serverlist_txt.open(QIODevice::WriteOnly | QIODevice::Append)) { - return; - } - - QTextStream out(&serverlist_txt); - out.setCodec("UTF-8"); - out << "\r\n" << p_line; - - serverlist_txt.close(); -} - QVector AOApplication::read_serverlist_txt() { QVector f_server_list; - QFile serverlist_txt; - QString serverlist_txt_path = get_base_path() + "serverlist.txt"; + QFile serverlist_txt(get_base_path() + "serverlist.txt"); + QFile serverlist_ini(get_base_path() + "favorite_servers.ini"); - serverlist_txt.setFileName(serverlist_txt_path); + if (serverlist_txt.exists() && !serverlist_ini.exists()) { + migrate_serverlist_txt(serverlist_txt); + } - if (serverlist_txt.open(QIODevice::ReadOnly)) { - QTextStream in(&serverlist_txt); - in.setCodec("UTF-8"); - - while (!in.atEnd()) { - QString line = in.readLine(); - server_type f_server; - QStringList line_contents = line.split(":"); - - if (line_contents.size() < 3) - continue; - - f_server.ip = line_contents.at(0); - f_server.port = line_contents.at(1).toInt(); - f_server.name = line_contents.at(2); - f_server.desc = ""; - - f_server_list.append(f_server); - } + if (serverlist_ini.exists()) { + QSettings l_favorite_ini(get_base_path() + "favorite_servers.ini", QSettings::IniFormat); + l_favorite_ini.setIniCodec("UTF-8"); + for(QString &fav_index: l_favorite_ini.childGroups()) { + server_type f_server; + l_favorite_ini.beginGroup(fav_index); + f_server.ip = l_favorite_ini.value("address", "127.0.0.1").toString(); + f_server.port = l_favorite_ini.value("port", 27016).toInt(); + f_server.name = l_favorite_ini.value("name", "Missing Name").toString(); + f_server.socket_type = to_connection_type.value(l_favorite_ini.value("protocol", "tcp").toString()); + f_server_list.append(f_server); + l_favorite_ini.endGroup(); + } } server_type demo_server; @@ -294,6 +272,43 @@ QVector AOApplication::read_serverlist_txt() return f_server_list; } +void AOApplication::migrate_serverlist_txt(QFile &p_serverlist_txt) +{ + // We migrate our legacy serverlist.txt to a QSettings object. + // Then we write it to disk. + QSettings l_settings(get_base_path() + "favorite_servers.ini", QSettings::IniFormat); + l_settings.setIniCodec("UTF-8"); + if (p_serverlist_txt.open(QIODevice::ReadOnly)) { + QTextStream l_favorite_textstream(&p_serverlist_txt); + l_favorite_textstream.setCodec("UTF-8"); + int l_entry_index = 0; + + while (!l_favorite_textstream.atEnd()) { + QString l_favorite_line = l_favorite_textstream.readLine(); + QStringList l_line_contents = l_favorite_line.split(":"); + + if (l_line_contents.size() >= 3) { + l_settings.beginGroup(QString::number(l_entry_index)); + l_settings.setValue("name", l_line_contents.at(2)); + l_settings.setValue("address", l_line_contents.at(0)); + l_settings.setValue("port", l_line_contents.at(1)); + + if (l_line_contents.size() >= 4) { + l_settings.setValue("protocol", l_line_contents.at(3)); + } + else { + l_settings.setValue("protocol","tcp"); + } + l_settings.endGroup(); + l_entry_index++; + } + } + l_settings.sync(); + } + p_serverlist_txt.close(); + p_serverlist_txt.rename(get_base_path() + "serverlist_depricated.txt"); +} + QString AOApplication::read_design_ini(QString p_identifier, VPath p_design_path) {