massive refactor ty longbyte
This commit is contained in:
parent
676b4cbf56
commit
772e850d37
@ -1,4 +1,4 @@
|
||||
QT += core gui network
|
||||
QT += core gui network websockets
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
|
@ -1,13 +1,19 @@
|
||||
#ifndef AOCLIENT_H
|
||||
#define AOCLIENT_H
|
||||
|
||||
#include "include/aopacket.h"
|
||||
#include "include/server.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
#include <QHostAddress>
|
||||
#include <QTcpSocket>
|
||||
|
||||
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;
|
||||
};
|
||||
|
@ -4,6 +4,7 @@
|
||||
#include "include/aoclient.h"
|
||||
#include "include/aopacket.h"
|
||||
#include "include/area_data.h"
|
||||
#include "include/ws_proxy.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDebug>
|
||||
@ -14,37 +15,35 @@
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
|
||||
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<AreaData*> 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<QTcpSocket*, AOClient*> clients;
|
||||
QString partial_packet;
|
||||
bool is_partial;
|
||||
|
||||
int player_count;
|
||||
QStringList characters;
|
||||
QVector<AreaData*> areas;
|
||||
QVector<AOClient*> clients;
|
||||
};
|
||||
|
||||
#endif // SERVER_H
|
||||
|
22
include/ws_proxy.h
Normal file
22
include/ws_proxy.h
Normal file
@ -0,0 +1,22 @@
|
||||
#ifndef WS_PROXY_H
|
||||
#define WS_PROXY_H
|
||||
|
||||
#include <QMap>
|
||||
#include <QTcpSocket>
|
||||
#include <QtWebSockets/QtWebSockets>
|
||||
|
||||
class WSProxy : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
WSProxy(QObject* parent);
|
||||
|
||||
public slots:
|
||||
void wsConnected();
|
||||
|
||||
private:
|
||||
QWebSocketServer* server;
|
||||
QMap<QWebSocket*, QTcpSocket*> tcp_sockets;
|
||||
QMap<QTcpSocket*, QWebSocket*> web_sockets;
|
||||
};
|
||||
|
||||
#endif // WS_PROXY_H
|
162
src/aoclient.cpp
162
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; }
|
||||
|
192
src/server.cpp
192
src/server.cpp
@ -48,117 +48,29 @@ 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<QTcpSocket*>(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);
|
||||
}
|
||||
}
|
||||
|
||||
void Server::clientData()
|
||||
{
|
||||
if (QTcpSocket* client = dynamic_cast<QTcpSocket*>(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)
|
||||
@ -167,87 +79,21 @@ void Server::handlePacket(AOPacket packet, QTcpSocket* socket)
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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;
|
||||
|
10
src/ws_proxy.cpp
Normal file
10
src/ws_proxy.cpp
Normal file
@ -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() {}
|
Loading…
Reference in New Issue
Block a user