From 772e850d37a7dcd5ed1807c8d102f460ff1f61ce Mon Sep 17 00:00:00 2001 From: scatterflower Date: Wed, 26 Aug 2020 01:28:00 -0500 Subject: [PATCH] massive refactor ty longbyte --- akashi.pro | 2 +- include/aoclient.h | 23 ++++- include/server.h | 25 +++--- include/ws_proxy.h | 22 +++++ src/aoclient.cpp | 162 ++++++++++++++++++++++++++++++++++- src/server.cpp | 206 ++++++--------------------------------------- src/ws_proxy.cpp | 10 +++ 7 files changed, 252 insertions(+), 198 deletions(-) create mode 100644 include/ws_proxy.h create mode 100644 src/ws_proxy.cpp diff --git a/akashi.pro b/akashi.pro index 14c0246..3deb207 100644 --- a/akashi.pro +++ b/akashi.pro @@ -1,4 +1,4 @@ -QT += core gui network +QT += core gui network websockets greaterThan(QT_MAJOR_VERSION, 4): QT += widgets diff --git a/include/aoclient.h b/include/aoclient.h index 349abe2..180daef 100644 --- a/include/aoclient.h +++ b/include/aoclient.h @@ -1,13 +1,19 @@ #ifndef AOCLIENT_H #define AOCLIENT_H +#include "include/aopacket.h" +#include "include/server.h" + #include #include #include -class AOClient { +class Server; + +class AOClient : public QObject { + Q_OBJECT public: - AOClient(QHostAddress p_remote_ip); + AOClient(Server* p_server, QTcpSocket* p_socket, QObject* parent = nullptr); ~AOClient(); QString getHwid(); @@ -21,7 +27,20 @@ class AOClient { int current_area; QString current_char; + public slots: + void clientDisconnected(); + void clientData(); + void sendPacket(AOPacket packet); + private: + Server* server; + QTcpSocket* socket; + + void handlePacket(AOPacket packet); + + QString partial_packet; + bool is_partial; + QString hwid; QString ipid; }; diff --git a/include/server.h b/include/server.h index 0ef90e8..dfe8e14 100644 --- a/include/server.h +++ b/include/server.h @@ -4,6 +4,7 @@ #include "include/aoclient.h" #include "include/aopacket.h" #include "include/area_data.h" +#include "include/ws_proxy.h" #include #include @@ -14,37 +15,35 @@ #include #include +class AOClient; + class Server : public QObject { Q_OBJECT public: Server(int p_port, int p_ws_port, QObject* parent = nullptr); void start(); + AOClient* getClient(QString ipid); + void updateCharsTaken(AreaData* area); + void broadcast(AOPacket packet); + + int player_count; + QStringList characters; + QVector areas; signals: public slots: void clientConnected(); - void clientDisconnected(); - void clientData(); private: - void handlePacket(AOPacket packet, QTcpSocket* socket); - QTcpSocket* getClient(QString ipid); - void broadcast(AOPacket packet); - + WSProxy* proxy; QTcpServer* server; int port; int ws_port; - QMap clients; - QString partial_packet; - bool is_partial; - - int player_count; - QStringList characters; - QVector areas; + QVector clients; }; #endif // SERVER_H diff --git a/include/ws_proxy.h b/include/ws_proxy.h new file mode 100644 index 0000000..5023527 --- /dev/null +++ b/include/ws_proxy.h @@ -0,0 +1,22 @@ +#ifndef WS_PROXY_H +#define WS_PROXY_H + +#include +#include +#include + +class WSProxy : public QObject { + Q_OBJECT + public: + WSProxy(QObject* parent); + + public slots: + void wsConnected(); + + private: + QWebSocketServer* server; + QMap tcp_sockets; + QMap web_sockets; +}; + +#endif // WS_PROXY_H diff --git a/src/aoclient.cpp b/src/aoclient.cpp index 006d582..6ae4900 100644 --- a/src/aoclient.cpp +++ b/src/aoclient.cpp @@ -1,12 +1,170 @@ #include "include/aoclient.h" -AOClient::AOClient(QHostAddress p_remote_ip) +AOClient::AOClient(Server* p_server, QTcpSocket* p_socket, QObject* parent) + : QObject(parent) { + socket = p_socket; + server = p_server; joined = false; password = ""; current_area = 0; current_char = ""; - remote_ip = p_remote_ip; + remote_ip = p_socket->peerAddress(); +} + +void AOClient::clientData() +{ + QString data = QString::fromUtf8(socket->readAll()); + // qDebug() << "From" << client->peerAddress() << ":" << data; + + if (is_partial) { + data = partial_packet + data; + } + if (!data.endsWith("%")) { + is_partial = true; + } + + QStringList all_packets = data.split("%"); + all_packets.removeLast(); // Remove the entry after the last delimiter + + for (QString single_packet : all_packets) { + AOPacket packet(single_packet); + handlePacket(packet); + } +} + +void AOClient::clientDisconnected() +{ + qDebug() << remote_ip << "disconnected"; + if (joined) + server->player_count--; + if (current_char != "") + server->areas.value(current_area)->characters_taken[current_char] = + false; + server->updateCharsTaken(server->areas.value(current_area)); +} + +void AOClient::handlePacket(AOPacket packet) +{ + // TODO: like everything here should send a signal + qDebug() << "Received packet:" << packet.header << ":" << packet.contents; + AreaData* area = server->areas.value(current_area); + // Lord forgive me + if (packet.header == "HI") { + setHwid(packet.contents[0]); + + AOPacket response( + "ID", {"271828", "akashi", QApplication::applicationVersion()}); + sendPacket(response); + } + else if (packet.header == "ID") { + QSettings config("config.ini", QSettings::IniFormat); + config.beginGroup("Options"); + QString max_players = config.value("max_players").toString(); + config.endGroup(); + + // Full feature list as of AO 2.8.5 + // The only ones that are critical to ensuring the server works are + // "noencryption" and "fastloading" + // TODO: make the rest of these user configurable + QStringList feature_list = { + "noencryption", "yellowtext", "prezoom", + "flipping", "customobjections", "fastloading", + "deskmod", "evidence", "cccc_ic_support", + "arup", "casing_alserts", "modcall_reason", + "looping_sfx", "additive", "effects"}; + AOPacket response_pn( + "PN", {QString::number(server->player_count), max_players}); + AOPacket response_fl("FL", feature_list); + sendPacket(response_pn); + sendPacket(response_fl); + } + else if (packet.header == "askchaa") { + // TODO: add user configurable content + // For testing purposes, we will just send enough to get things working + AOPacket response( + "SI", {QString::number(server->characters.length()), "0", "1"}); + sendPacket(response); + } + else if (packet.header == "RC") { + AOPacket response("SC", server->characters); + sendPacket(response); + } + else if (packet.header == "RM") { + AOPacket response("SM", {"~stop.mp3"}); + sendPacket(response); + } + else if (packet.header == "RD") { + server->player_count++; + joined = true; + + server->updateCharsTaken(area); + AOPacket response_op("OPPASS", {"DEADBEEF"}); + AOPacket response_done("DONE", {}); + sendPacket(response_op); + sendPacket(response_done); + } + else if (packet.header == "PW") { + password = packet.contents[0]; + } + else if (packet.header == "CC") { + bool argument_ok; + int char_id = packet.contents[1].toInt(&argument_ok); + if (!argument_ok) + return; + + if (current_char != "") { + area->characters_taken[current_char] = false; + } + + if (char_id >= 0) { + QString char_selected = server->characters[char_id]; + bool taken = area->characters_taken.value(char_selected); + if (taken || char_selected == "") + return; + + area->characters_taken[char_selected] = true; + current_char = char_selected; + } + else { + current_char = ""; + } + + server->updateCharsTaken(server->areas.value(current_area)); + AOPacket response_pv("PV", {"271828", "CID", packet.contents[1]}); + sendPacket(response_pv); + } + else if (packet.header == "MS") { + // TODO: validate, validate, validate + server->broadcast(packet); + } + else if (packet.header == "CT") { + // TODO: commands + // TODO: zalgo strip + server->broadcast(packet); + } + else if (packet.header == "CH") { + // Why does this packet exist + AOPacket response("CHECK", {}); + sendPacket(response); + } + else if (packet.header == "whoami") { + AOPacket response( + "CT", {"Made with love", "by scatterflower and windrammer"}); + sendPacket(response); + } + else { + qDebug() << "Unimplemented packet:" << packet.header; + qDebug() << packet.contents; + } + socket->flush(); +} + +void AOClient::sendPacket(AOPacket packet) +{ + qDebug() << "Sent packet:" << packet.header << ":" << packet.contents; + socket->write(packet.toUtf8()); + socket->flush(); } QString AOClient::getHwid() { return hwid; } diff --git a/src/server.cpp b/src/server.cpp index 4c63292..1062181 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -48,206 +48,52 @@ void Server::start() void Server::clientConnected() { - QTcpSocket* client = server->nextPendingConnection(); - AOClient* ao_client = new AOClient(client->peerAddress()); - clients.insert(client, ao_client); - connect(client, SIGNAL(disconnected()), this, SLOT(clientDisconnected())); - connect(client, SIGNAL(readyRead()), this, SLOT(clientData())); + QTcpSocket* socket = server->nextPendingConnection(); + AOClient* client = new AOClient(this, socket, this); + clients.append(client); + connect(socket, &QTcpSocket::disconnected, client, + &AOClient::clientDisconnected); + connect(socket, &QTcpSocket::disconnected, this, [=] { + qDebug() << "removed client" << client->getIpid(); + clients.removeAll(client); + delete client; + }); + connect(socket, &QTcpSocket::readyRead, client, &AOClient::clientData); AOPacket decryptor( "decryptor", {"NOENCRYPT"}); // This is the infamous workaround for // tsuserver4 It should disable fantacrypt // completely in any client 2.4.3 or newer - client->write(decryptor.toUtf8()); + client->sendPacket(decryptor); - qDebug() << client->peerAddress().toString() << "connected"; + qDebug() << client->remote_ip.toString() << "connected"; } -void Server::clientDisconnected() +void Server::updateCharsTaken(AreaData* area) { - if (QTcpSocket* client = dynamic_cast(sender())) { - qDebug() << client->peerAddress() << "disconnected"; - AOClient* ao_client = clients.value(client); - if (ao_client->joined) - player_count--; - if(ao_client->current_char != "") - areas.value(ao_client->current_area) - ->characters_taken[ao_client->current_char] = false; - - delete ao_client; - clients.remove(client); + QStringList chars_taken; + for (QString cur_char : area->characters_taken.keys()) { + chars_taken.append(area->characters_taken.value(cur_char) + ? QStringLiteral("-1") + : QStringLiteral("0")); } -} -void Server::clientData() -{ - if (QTcpSocket* client = dynamic_cast(sender())) { - QString data = QString::fromUtf8(client->readAll()); - // qDebug() << "From" << client->peerAddress() << ":" << data; - - if (is_partial) { - data = partial_packet + data; - } - if (!data.endsWith("%")) { - is_partial = true; - } - - QStringList all_packets = data.split("%"); - all_packets.removeLast(); // Remove the entry after the last delimiter - - for (QString single_packet : all_packets) { - AOPacket packet(single_packet); - handlePacket(packet, client); - } - } -} - -void Server::handlePacket(AOPacket packet, QTcpSocket* socket) -{ - // TODO: like everything here should send a signal - qDebug() << "Received packet:" << packet.header << ":" << packet.contents; - AOClient* client = clients.value(socket); - AreaData* area = areas.value(client->current_area); - // Lord forgive me - if (packet.header == "HI") { - AOClient* client = clients.value(socket); - client->setHwid(packet.contents[0]); - - AOPacket response( - "ID", {"271828", "akashi", QApplication::applicationVersion()}); - socket->write(response.toUtf8()); - } - else if (packet.header == "ID") { - QSettings config("config.ini", QSettings::IniFormat); - config.beginGroup("Options"); - QString max_players = config.value("max_players").toString(); - config.endGroup(); - - // Full feature list as of AO 2.8.5 - // The only ones that are critical to ensuring the server works are - // "noencryption" and "fastloading" - // TODO: make the rest of these user configurable - QStringList feature_list = { - "noencryption", "yellowtext", "prezoom", - "flipping", "customobjections", "fastloading", - "deskmod", "evidence", "cccc_ic_support", - "arup", "casing_alserts", "modcall_reason", - "looping_sfx", "additive", "effects"}; - AOPacket response_pn("PN", - {QString::number(player_count), max_players}); - AOPacket response_fl("FL", feature_list); - socket->write(response_pn.toUtf8()); - socket->write(response_fl.toUtf8()); - } - else if (packet.header == "askchaa") { - // TODO: add user configurable content - // For testing purposes, we will just send enough to get things working - AOPacket response("SI", - {QString::number(characters.length()), "0", "1"}); - qDebug() << response.toString(); - socket->write(response.toUtf8()); - } - else if (packet.header == "RC") { - AOPacket response("SC", characters); - socket->write(response.toUtf8()); - } - else if (packet.header == "RM") { - AOPacket response("SM", {"~stop.mp3"}); - socket->write(response.toUtf8()); - } - else if (packet.header == "RD") { - player_count++; - client->joined = true; - - QStringList chars_taken; - for (QString cur_char : area->characters_taken.keys()) { - chars_taken.append(area->characters_taken.value(cur_char) - ? QStringLiteral("-1") - : QStringLiteral("0")); - } - - AOPacket response_cc("CharsCheck", chars_taken); - AOPacket response_op("OPPASS", {"DEADBEEF"}); - AOPacket response_done("DONE", {}); - broadcast(response_cc); - socket->write(response_op.toUtf8()); - socket->write(response_done.toUtf8()); - } - else if (packet.header == "PW") { - client->password = packet.contents[0]; - } - else if (packet.header == "CC") { - bool argument_ok; - int char_id = packet.contents[1].toInt(&argument_ok); - if (!argument_ok) - return; - - if (client->current_char != "") { - area->characters_taken[client->current_char] = false; - } - - if(char_id >= 0) { - QString char_selected = characters[char_id]; - bool taken = area->characters_taken.value(char_selected); - if (taken || char_selected == "") - return; - - area->characters_taken[char_selected] = true; - client->current_char = char_selected; - } else { - client->current_char = ""; - } - - QStringList chars_taken; - for (QString cur_char : area->characters_taken.keys()) { - chars_taken.append(area->characters_taken.value(cur_char) - ? QStringLiteral("-1") - : QStringLiteral("0")); - } - - AOPacket response_cc("CharsCheck", chars_taken); - AOPacket response_pv("PV", {"271828", "CID", packet.contents[1]}); - socket->write(response_pv.toUtf8()); - broadcast(response_cc); - } - else if (packet.header == "MS") { - // TODO: validate, validate, validate - broadcast(packet); - } - else if (packet.header == "CT") { - // TODO: commands - // TODO: zalgo strip - broadcast(packet); - } - else if (packet.header == "CH") { - // Why does this packet exist - AOPacket response("CHECK", {}); - socket->write(response.toUtf8()); - } - else if (packet.header == "what") { - AOPacket response( - "CT", {"Made with love", "by scatterflower and windrammer"}); - } - else { - qDebug() << "Unimplemented packet:" << packet.header; - qDebug() << packet.contents; - } - socket->flush(); + AOPacket response_cc("CharsCheck", chars_taken); + broadcast(response_cc); } void Server::broadcast(AOPacket packet) { // TODO: make this selective to the current area only - for (QTcpSocket* client : clients.keys()) { - client->write(packet.toUtf8()); - client->flush(); + for (AOClient* client : clients) { + client->sendPacket(packet); } } -QTcpSocket* Server::getClient(QString ipid) +AOClient* Server::getClient(QString ipid) { - for (QTcpSocket* client : clients.keys()) { - if (clients.value(client)->getIpid() == ipid) + for (AOClient* client : clients) { + if (client->getIpid() == ipid) return client; } return nullptr; diff --git a/src/ws_proxy.cpp b/src/ws_proxy.cpp new file mode 100644 index 0000000..5a1dc99 --- /dev/null +++ b/src/ws_proxy.cpp @@ -0,0 +1,10 @@ +#include "include/ws_proxy.h" + +WSProxy::WSProxy(QObject* parent) : QObject(parent) +{ + server = new QWebSocketServer(QStringLiteral(""), + QWebSocketServer::NonSecureMode, this); + connect(server, SIGNAL(newConnection()), this, SLOT(wsConnected())); +} + +void WSProxy::wsConnected() {}