aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSalanto <62221668+Salanto@users.noreply.github.com>2022-06-06 10:14:44 -0700
committerGitHub <noreply@github.com>2022-06-06 19:14:44 +0200
commitf0a5e48f5ca6834c0f24b167c904a030894f706e (patch)
tree0e7ae1497d0164e3a1a3c1d61a88e4f719beb5b5
parentc4f459b6cce6382cbd7c1960a6738a5a8a45ab8c (diff)
Dual-Stack AO2 Client to handle both TCP and Websocket connections seemlessly (#696)
* Replace TCP Serversocket with Websocket * Have TCP sockets live harmoniously with WS "like 5 lines" yeah probably lost a bet. * Update .gitlab-ci.yml * hack to fix favorites * Add support for websockets in the favorites list (serverlist.txt) Make "add_favorite_server" remember the socket type * Preserve old serverlist style This will keep new entries compatible with 2.9 and prior clients. Makes parsing the list easier too. * Add lookup table and correct write code to use lowercase * I have no idea what a lookup table is, but this looks close enough * Fix lookup table * Otherwise backend selection behaviour is inverted * clang-tidy had one job * Yet it did not do it. Co-authored-by: oldmud0 <oldmud0@users.noreply.github.com> * const p_data * Switch serverlist.txt to an ini format * Fixes an Omni bug where : would split the servername * Utilises internally QSettings properly for low parsing effort and clear structure * Automatically migrates the legacy serverlist.txt to favorite_servers.ini * Pleases my OCD * Replace sample serverlist. Co-authored-by: oldmud0 <oldmud0@users.noreply.github.com> Co-authored-by: stonedDiscord <Tukz@gmx.de> Co-authored-by: Alex Noir <Varsash@gmail.com>
-rw-r--r--Attorney_Online.pro2
-rw-r--r--CMakeLists.txt4
-rw-r--r--base/favorite_servers_sample.ini5
-rw-r--r--base/serverlist.sample.txt1
-rw-r--r--include/aoapplication.h10
-rw-r--r--include/datatypes.h12
-rw-r--r--include/networkmanager.h25
-rw-r--r--src/aoapplication.cpp18
-rw-r--r--src/networkmanager.cpp108
-rw-r--r--src/text_file_functions.cpp95
10 files changed, 205 insertions, 75 deletions
diff --git a/Attorney_Online.pro b/Attorney_Online.pro
index 1fb5d0b9..95e39458 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 12a63c30..9d7714b6 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 00000000..10126d20
--- /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 f700836c..00000000
--- 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 4809c3fc..4c3a83f3 100644
--- a/include/aoapplication.h
+++ b/include/aoapplication.h
@@ -133,6 +133,8 @@ public:
void set_favorite_list();
QVector<server_type> &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<server_type> &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<server_type> 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 2f03d39f..3289bd86 100644
--- a/include/datatypes.h
+++ b/include/datatypes.h
@@ -1,13 +1,25 @@
#ifndef DATATYPES_H
#define DATATYPES_H
+#include <QMap>
#include <QString>
+enum connection_type {
+ TCP,
+ WEBSOCKETS,
+};
+
+static QMap<QString, connection_type> 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 26a61f2d..705fb9ea 100644
--- a/include/networkmanager.h
+++ b/include/networkmanager.h
@@ -6,7 +6,7 @@
#include <QDnsLookup>
#include <QNetworkAccessManager>
-#include <QTcpSocket>
+#include <QtWebSockets/QWebSocket>
#include <QTime>
#include <QTimer>
@@ -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<void()> &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<void(QString)> &cb);
diff --git a/src/aoapplication.cpp b/src/aoapplication.cpp
index 13c995f2..1e70ca93 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 4c823852..8f419fce 100644
--- a/src/networkmanager.cpp
+++ b/src/networkmanager.cpp
@@ -4,6 +4,7 @@
#include "debug_functions.h"
#include "lobby.h"
+#include <QAbstractSocket>
#include <QJsonArray>
#include <QJsonDocument>
#include <QNetworkReply>
@@ -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<QString>();
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<QAbstractSocket::SocketError>::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 b5bd6968..0dde68f6 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<server_type> AOApplication::read_serverlist_txt()
{
QVector<server_type> f_server_list;
- QFile serverlist_txt;
- QString serverlist_txt_path = get_base_path() + "serverlist.txt";
-
- serverlist_txt.setFileName(serverlist_txt_path);
-
- if (serverlist_txt.open(QIODevice::ReadOnly)) {
- QTextStream in(&serverlist_txt);
- in.setCodec("UTF-8");
+ QFile serverlist_txt(get_base_path() + "serverlist.txt");
+ QFile serverlist_ini(get_base_path() + "favorite_servers.ini");
- 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 = "";
+ if (serverlist_txt.exists() && !serverlist_ini.exists()) {
+ migrate_serverlist_txt(serverlist_txt);
+ }
- 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<server_type> 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)
{