diff --git a/akashi.pro b/akashi.pro
index dac5e79..5f8b601 100644
--- a/akashi.pro
+++ b/akashi.pro
@@ -15,6 +15,8 @@ DEFINES += QT_DEPRECATED_WARNINGS
# You can also select to disable deprecated APIs only up to a certain version of Qt.
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
+QMAKE_CXXFLAGS_WARN_OFF -= -Wunused-parameter
+
DESTDIR = $$PWD/bin
OBJECTS_DIR = $$PWD/build
MOC_DIR = $$PWD/build
@@ -28,7 +30,6 @@ SOURCES += src/advertiser.cpp \
src/commands.cpp \
src/config_manager.cpp \
src/db_manager.cpp \
- src/icchatpacket.cpp \
src/main.cpp \
src/packets.cpp \
src/server.cpp \
@@ -42,7 +43,6 @@ HEADERS += include/advertiser.h \
include/area_data.h \
include/config_manager.h \
include/db_manager.h \
- include/icchatpacket.h \
include/server.h \
include/ws_client.h \
include/ws_proxy.h
diff --git a/include/aoclient.h b/include/aoclient.h
index 369768d..f763131 100644
--- a/include/aoclient.h
+++ b/include/aoclient.h
@@ -20,7 +20,6 @@
#include "include/aopacket.h"
#include "include/server.h"
-#include "include/icchatpacket.h"
#include "include/area_data.h"
#include "include/db_manager.h"
@@ -106,6 +105,13 @@ class AOClient : public QObject {
void pktHpBar(AreaData* area, int argc, QStringList argv, AOPacket packet);
void pktWebSocketIp(AreaData* area, int argc, QStringList argv, AOPacket packet);
+ // Packet helper functions
+ AOPacket validateIcPacket(AOPacket packet);
+
+ // Packet helper global variables
+ bool last_msg_blankpost = false;
+ int char_id = -1;
+
struct PacketInfo {
unsigned long long acl_mask;
int minArgs;
@@ -121,7 +127,7 @@ class AOClient : public QObject {
{"RD", {ACLFlags.value("NONE"), 0, &AOClient::pktLoadingDone}},
{"PW", {ACLFlags.value("NONE"), 1, &AOClient::pktCharPassword}},
{"CC", {ACLFlags.value("NONE"), 3, &AOClient::pktSelectChar}},
- {"MS", {ACLFlags.value("NONE"), 1, &AOClient::pktIcChat}}, // TODO: doublecheck
+ {"MS", {ACLFlags.value("NONE"), 15, &AOClient::pktIcChat}},
{"CT", {ACLFlags.value("NONE"), 2, &AOClient::pktOocChat}},
{"CH", {ACLFlags.value("NONE"), 1, &AOClient::pktPing}},
{"MC", {ACLFlags.value("NONE"), 2, &AOClient::pktChangeMusic}},
@@ -147,6 +153,7 @@ class AOClient : public QObject {
void cmdAddPerms(int argc, QStringList argv);
void cmdRemovePerms(int argc, QStringList argv);
void cmdListUsers(int argc, QStringList argv);
+ void cmdLogout(int argc, QStringList argv);
// Command helper functions
QStringList buildAreaList(int area_idx);
@@ -176,7 +183,8 @@ class AOClient : public QObject {
{"listperms", {ACLFlags.value("NONE"), 0, &AOClient::cmdListPerms}},
{"addperm", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdAddPerms}},
{"removeperm", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdRemovePerms}},
- {"listusers", {ACLFlags.value("MODIFY_USERS"), 0, &AOClient::cmdListUsers}}
+ {"listusers", {ACLFlags.value("MODIFY_USERS"), 0, &AOClient::cmdListUsers}},
+ {"logout", {ACLFlags.value("NONE"), 0, &AOClient::cmdLogout}}
};
QString partial_packet;
diff --git a/include/db_manager.h b/include/db_manager.h
index c6d466d..150d751 100644
--- a/include/db_manager.h
+++ b/include/db_manager.h
@@ -40,7 +40,7 @@ public:
void addBan(QString ipid, QHostAddress ip, QString hdid, unsigned long time, QString reason);
- void createUser(QString username, QString salt, QString password, unsigned long long acl);
+ bool createUser(QString username, QString salt, QString password, unsigned long long acl);
unsigned long long getACL(QString moderator_name);
bool authenticate(QString username, QString password);
bool updateACL(QString username, unsigned long long acl, bool mode);
diff --git a/include/icchatpacket.h b/include/icchatpacket.h
deleted file mode 100644
index 8d9e507..0000000
--- a/include/icchatpacket.h
+++ /dev/null
@@ -1,65 +0,0 @@
-//////////////////////////////////////////////////////////////////////////////////////
-// akashi - a server for Attorney Online 2 //
-// Copyright (C) 2020 scatterflower //
-// //
-// This program is free software: you can redistribute it and/or modify //
-// it under the terms of the GNU Affero General Public License as //
-// published by the Free Software Foundation, either version 3 of the //
-// License, or (at your option) any later version. //
-// //
-// This program is distributed in the hope that it will be useful, //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
-// GNU Affero General Public License for more details. //
-// //
-// You should have received a copy of the GNU Affero General Public License //
-// along with this program. If not, see . //
-//////////////////////////////////////////////////////////////////////////////////////
-#ifndef ICCHATPACKET_H
-#define ICCHATPACKET_H
-
-#endif // ICCHATPACKET_H
-
-#include "include/aopacket.h"
-
-class ICChatPacket : public AOPacket
-{
-public:
- ICChatPacket(AOPacket packet);
-
- const int MIN_FIELDS = 15;
- const int MAX_FIELDS = 30;
-
- bool is_valid;
-
- QString desk_override;
- QString preanim;
- QString character;
- QString emote;
- QString message;
- QString side;
- QString sfx_name;
- int emote_modifier;
- int char_id;
- QString sfx_delay;
- int objection_modifier;
- QString evidence;
- bool flip;
- bool realization;
- int text_color;
- QString showname;
- int other_charid;
- QString other_name;
- QString other_emote;
- int self_offset;
- int other_offset;
- bool other_flip;
- bool noninterrupting_preanim;
- bool sfx_looping;
- bool screenshake;
- QString frames_shake;
- QString frames_realization;
- QString frames_sfx;
- bool additive;
- QString effect;
-};
diff --git a/src/aoclient.cpp b/src/aoclient.cpp
index a3eccd5..afea8de 100644
--- a/src/aoclient.cpp
+++ b/src/aoclient.cpp
@@ -70,8 +70,7 @@ void AOClient::clientDisconnected()
void AOClient::handlePacket(AOPacket packet)
{
- // TODO: like everything here should send a signal
- // qDebug() << "Received packet:" << packet.header << ":" << packet.contents;
+ qDebug() << "Received packet:" << packet.header << ":" << packet.contents << "args length:" << packet.contents.length();
AreaData* area = server->areas[current_area];
PacketInfo info = packets.value(packet.header, {false, 0, &AOClient::pktDefault});
@@ -175,7 +174,7 @@ void AOClient::fullArup() {
void AOClient::sendPacket(AOPacket packet)
{
- // qDebug() << "Sent packet:" << packet.header << ":" << packet.contents;
+ qDebug() << "Sent packet:" << packet.header << ":" << packet.contents;
socket->write(packet.toUtf8());
socket->flush();
}
diff --git a/src/commands.cpp b/src/commands.cpp
index 825f1a1..04338ee 100644
--- a/src/commands.cpp
+++ b/src/commands.cpp
@@ -209,8 +209,10 @@ void AOClient::cmdAddUser(int argc, QStringList argv)
quint64 salt_number = QRandomGenerator::system()->generate64();
QString salt = QStringLiteral("%1").arg(salt_number, 16, 16, QLatin1Char('0'));
- server->db_manager->createUser(argv[0], salt, argv[1], ACLFlags.value("NONE"));
- sendServerMessage("Created user " + argv[0] + ".\nUse /addperm to modify their permissions.");
+ if (server->db_manager->createUser(argv[0], salt, argv[1], ACLFlags.value("NONE")))
+ sendServerMessage("Created user " + argv[0] + ".\nUse /addperm to modify their permissions.");
+ else
+ sendServerMessage("Unable to create user " + argv[0] + ".\nDoes a user with that name already exist?");
}
void AOClient::cmdListPerms(int argc, QStringList argv)
@@ -232,7 +234,7 @@ void AOClient::cmdListPerms(int argc, QStringList argv)
}
}
else {
- if ((user_acl & ACLFlags.value("MANAGE_USERS")) == 0) {
+ if ((user_acl & ACLFlags.value("MODIFY_USERS")) == 0) {
sendServerMessage("You do not have permission to view other users' permissions.");
return;
}
@@ -297,6 +299,11 @@ void AOClient::cmdRemovePerms(int argc, QStringList argv)
return;
}
+ if (argv[0] == "root") {
+ sendServerMessage("You cannot change the permissions of the root account!");
+ return;
+ }
+
if (argv[1] == "SUPER") {
if (user_acl != ACLFlags.value("SUPER")) {
// This has to be checked separately, because SUPER & anything will always be truthy
@@ -327,6 +334,18 @@ void AOClient::cmdListUsers(int argc, QStringList argv)
sendServerMessage("All users:\n" + users.join("\n"));
}
+void AOClient::cmdLogout(int argc, QStringList argv)
+{
+ if (!authenticated) {
+ sendServerMessage("You are not logged in!");
+ return;
+ }
+ authenticated = false;
+ moderator_name = "";
+ sendServerMessage("You have been logged out.");
+ return;
+}
+
QStringList AOClient::buildAreaList(int area_idx)
{
QStringList entries;
@@ -335,7 +354,7 @@ QStringList AOClient::buildAreaList(int area_idx)
entries.append("=== " + area_name + " ===");
entries.append("[" + QString::number(area->player_count) + " users][" + area->status + "]");
for (AOClient* client : server->clients) {
- if (client->current_area == area_idx && client->authenticated) {
+ if (client->current_area == area_idx && client->joined) {
QString char_entry = client->current_char;
if (char_entry == "")
char_entry = "Spectator";
diff --git a/src/db_manager.cpp b/src/db_manager.cpp
index 106b145..97de549 100644
--- a/src/db_manager.cpp
+++ b/src/db_manager.cpp
@@ -87,8 +87,16 @@ void DBManager::addBan(QString ipid, QHostAddress ip, QString hdid, unsigned lon
qDebug() << "SQL Error:" << query.lastError().text();
}
-void DBManager::createUser(QString username, QString salt, QString password, unsigned long long acl)
+bool DBManager::createUser(QString username, QString salt, QString password, unsigned long long acl)
{
+ QSqlQuery username_exists;
+ username_exists.prepare("SELECT ACL FROM users WHERE USERNAME = ?");
+ username_exists.addBindValue(username);
+ username_exists.exec();
+
+ if (username_exists.first())
+ return false;
+
QSqlQuery query;
QString salted_password;
@@ -105,6 +113,7 @@ void DBManager::createUser(QString username, QString salt, QString password, uns
query.exec();
qDebug() << "Created user" << username << "with password" << password << "and salted with value" << salt << ": stored as" << salted_password;
+ return true;
}
unsigned long long DBManager::getACL(QString moderator_name)
diff --git a/src/icchatpacket.cpp b/src/icchatpacket.cpp
deleted file mode 100644
index 966a49e..0000000
--- a/src/icchatpacket.cpp
+++ /dev/null
@@ -1,31 +0,0 @@
-//////////////////////////////////////////////////////////////////////////////////////
-// akashi - a server for Attorney Online 2 //
-// Copyright (C) 2020 scatterflower //
-// //
-// This program is free software: you can redistribute it and/or modify //
-// it under the terms of the GNU Affero General Public License as //
-// published by the Free Software Foundation, either version 3 of the //
-// License, or (at your option) any later version. //
-// //
-// This program is distributed in the hope that it will be useful, //
-// but WITHOUT ANY WARRANTY; without even the implied warranty of //
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //
-// GNU Affero General Public License for more details. //
-// //
-// You should have received a copy of the GNU Affero General Public License //
-// along with this program. If not, see . //
-//////////////////////////////////////////////////////////////////////////////////////
-#include "include/icchatpacket.h"
-
-ICChatPacket::ICChatPacket(AOPacket packet)
- : AOPacket(packet.header, packet.contents)
-{
- // Perform some basic validation
- if (packet.contents.length() > MAX_FIELDS || packet.contents.length() < MIN_FIELDS) {
- is_valid = false;
- return;
- }
-
- // Populate information about the message
- is_valid = true;
-}
diff --git a/src/packets.cpp b/src/packets.cpp
index 11317cb..678c778 100644
--- a/src/packets.cpp
+++ b/src/packets.cpp
@@ -108,9 +108,11 @@ void AOClient::pktCharPassword(AreaData* area, int argc, QStringList argv, AOPac
void AOClient::pktSelectChar(AreaData* area, int argc, QStringList argv, AOPacket packet)
{
bool argument_ok;
- int char_id = argv[1].toInt(&argument_ok);
- if (!argument_ok)
+ char_id = argv[1].toInt(&argument_ok);
+ if (!argument_ok) {
+ char_id = -1;
return;
+ }
if (current_char != "") {
area->characters_taken[current_char] = false;
@@ -139,9 +141,11 @@ void AOClient::pktSelectChar(AreaData* area, int argc, QStringList argv, AOPacke
void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket packet)
{
// TODO: validate, validate, validate
- ICChatPacket ic_packet(packet);
- if (ic_packet.is_valid)
- server->broadcast(ic_packet, current_area);
+ AOPacket validated_packet = validateIcPacket(packet);
+ if (validated_packet.header == "INVALID")
+ return;
+
+ server->broadcast(validated_packet, current_area);
}
void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket packet)
@@ -229,3 +233,145 @@ void AOClient::pktWebSocketIp(AreaData* area, int argc, QStringList argv, AOPack
remote_ip = QHostAddress(argv[0]);
}
}
+
+AOPacket AOClient::validateIcPacket(AOPacket packet)
+{
+ // Welcome to the super cursed server-side IC chat validation hell
+
+ // I wanted to use enums or #defines here to make the
+ // indicies of the args arrays more readable. But,
+ // in typical AO fasion, the indicies for the incoming
+ // and outgoing packets are different. Just RTFM.
+
+ AOPacket invalid("INVALID", {});
+
+ QStringList args;
+ if (current_char == "" || !joined)
+ // Spectators cannot use IC
+ return invalid;
+
+ QList incoming_args;
+ for (QString arg : packet.contents) {
+ incoming_args.append(QVariant(arg));
+ }
+
+ // message type
+ if (incoming_args[0].toInt() == 1)
+ args.append("1");
+ else if (incoming_args[0].toInt() == 0) {
+ if (incoming_args[0].toString() == "chat")
+ args.append("chat");
+ else
+ args.append("0");
+ }
+
+ // preanim
+ args.append(incoming_args[1].toString());
+
+ // char name
+ if (!server->characters.contains(incoming_args[2].toString()))
+ return invalid;
+ if (current_char != incoming_args[2].toString()) {
+ // Selected char is different from supplied folder name
+ // This means the user is INI-swapped
+ // TODO: ini swap locking
+ qDebug() << "INI swap detected from " << getIpid();
+ }
+ args.append(incoming_args[2].toString());
+
+ // emote
+ args.append(incoming_args[3].toString());
+
+ // message text
+ QString incoming_msg = incoming_args[4].toString().trimmed();
+ if (incoming_msg == "") {
+ if (last_msg_blankpost)
+ return invalid;
+ last_msg_blankpost = true;
+ }
+ else
+ last_msg_blankpost = false;
+
+ if (incoming_msg == last_message)
+ return invalid;
+
+ last_message = incoming_msg;
+ args.append(incoming_msg);
+
+ // side
+ // this is validated clientside so w/e
+ args.append(incoming_args[5].toString());
+
+ // sfx name
+ args.append(incoming_args[6].toString());
+
+ // emote modifier
+ // Now, gather round, y'all. Here is a story that is truly a microcosm of the AO dev experience.
+ // If this value is a 4, it will crash the client. Why? Who knows, but it does.
+ // Now here is the kicker: in certain versions, the client would incorrectly send a 4 here
+ // For a long time, by configuring the client to do a zoom with a preanim, it would send 4
+ // This would crash everyone else's client, and the feature had to be disabled
+ // But, for some reason, nobody traced the cause of this issue for many many years.
+ // The serverside fix is needed to ensure invalid values are not sent, because the client sucks
+ int emote_mod = incoming_args[7].toInt();
+
+ if (emote_mod == 4)
+ emote_mod = 6;
+ if (emote_mod != 0 && emote_mod != 1 && emote_mod != 2 && emote_mod != 5 && emote_mod != 6)
+ return invalid;
+ args.append(QString::number(emote_mod));
+
+ // char id
+ if (incoming_args[8].toInt() != char_id)
+ return invalid;
+ args.append(incoming_args[8].toString());
+
+ // sfx delay
+ args.append(incoming_args[9].toString());
+
+ // objection modifier
+ if (incoming_args[10].toString().contains("4")) {
+ // custom shout includes text metadata
+ args.append(incoming_args[10].toString());
+ }
+ else {
+ int obj_mod = incoming_args[10].toInt();
+ if (obj_mod != 0 && obj_mod != 1 && obj_mod != 2 && obj_mod != 3)
+ return invalid;
+ args.append(QString::number(obj_mod));
+ }
+
+ // evidence
+ // TODO: add to this once evidence is implemented
+ args.append(incoming_args[11].toString());
+
+ // flipping
+ int flip = incoming_args[12].toInt();
+ if (flip != 0 && flip != 1)
+ return invalid;
+ args.append(QString::number(flip));
+
+ // realization
+ int realization = incoming_args[13].toInt();
+ if (realization != 0 && realization != 1)
+ return invalid;
+ args.append(QString::number(realization));
+
+ // text color
+ int text_color = incoming_args[14].toInt();
+ if (text_color != 0 && text_color != 1 && text_color != 2 && text_color != 3 && text_color != 4 && text_color != 5 && text_color != 6)
+ return invalid;
+ args.append(QString::number(text_color));
+
+ // 2.6 packet extensions
+ if (incoming_args.length() > 15) {
+
+ }
+
+ // 2.8 packet extensions
+ if (incoming_args.length() > 19) {
+
+ }
+
+ return AOPacket("MS", args);
+}