diff --git a/akashi.pro b/akashi.pro index 4c4b6e4..3ff90f9 100644 --- a/akashi.pro +++ b/akashi.pro @@ -30,6 +30,7 @@ SOURCES += src/advertiser.cpp \ src/config_manager.cpp \ src/icchatpacket.cpp \ src/main.cpp \ + src/packets.cpp \ src/server.cpp \ src/ws_client.cpp \ src/ws_proxy.cpp diff --git a/include/aoclient.h b/include/aoclient.h index 24ffe07..53c70cd 100644 --- a/include/aoclient.h +++ b/include/aoclient.h @@ -21,6 +21,7 @@ #include "include/aopacket.h" #include "include/server.h" #include "include/icchatpacket.h" +#include "include/area_data.h" #include @@ -74,6 +75,47 @@ class AOClient : public QObject { void fullArup(); void sendServerMessage(QString message); + // Packet headers + void pktDefault(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktHardwareId(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktSoftwareId(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktBeginLoad(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktRequestChars(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktRequestMusic(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktLoadingDone(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktCharPassword(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktSelectChar(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktPing(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktChangeMusic(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktWtCe(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktHpBar(AreaData* area, int argc, QStringList argv, AOPacket packet); + void pktWebSocketIp(AreaData* area, int argc, QStringList argv, AOPacket packet); + + struct PacketInfo { + int minArgs; + void (AOClient::*action)(AreaData*, int, QStringList, AOPacket); + }; + + const QMap packets { + {"HI", {1, &AOClient::pktHardwareId}}, + {"ID", {2, &AOClient::pktSoftwareId}}, + {"askchaa", {0, &AOClient::pktBeginLoad}}, + {"RC", {0, &AOClient::pktRequestChars}}, + {"RM", {0, &AOClient::pktRequestMusic}}, + {"RD", {0, &AOClient::pktLoadingDone}}, + {"PW", {1, &AOClient::pktCharPassword}}, + {"CC", {3, &AOClient::pktSelectChar}}, + {"MS", {1, &AOClient::pktIcChat}}, // TODO: doublecheck + {"CT", {2, &AOClient::pktOocChat}}, + {"CH", {1, &AOClient::pktPing}}, + {"MC", {2, &AOClient::pktChangeMusic}}, + {"RT", {1, &AOClient::pktWtCe}}, + {"HP", {2, &AOClient::pktHpBar}}, + {"WSIP", {1, &AOClient::pktWebSocketIp}} + }; + // Commands void cmdDefault(int argc, QStringList argv); void cmdLogin(int argc, QStringList argv); diff --git a/src/aoclient.cpp b/src/aoclient.cpp index d5c5afe..f40dfdf 100644 --- a/src/aoclient.cpp +++ b/src/aoclient.cpp @@ -71,179 +71,16 @@ 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; AreaData* area = server->areas[current_area]; - // Lord forgive me - if (packet.header == "HI") { - setHwid(packet.contents[0]); - if(server->ban_manager->isHDIDBanned(getHwid())) { - sendPacket("BD", {server->ban_manager->getBanReason(getHwid())}); - socket->close(); - return; - } - sendPacket("ID", {"271828", "akashi", QCoreApplication::applicationVersion()}); - } - else if (packet.header == "ID") { - QSettings config("config/config.ini", QSettings::IniFormat); - config.beginGroup("Options"); - QString max_players = config.value("max_players").toString(); - config.endGroup(); + PacketInfo info = packets.value(packet.header, {0, &AOClient::pktDefault}); - // 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_alerts", "modcall_reason", - "looping_sfx", "additive", "effects"}; + if (packet.contents.length() < info.minArgs) { + qDebug() << "Invalid packet args length. Minimum is" << info.minArgs << "but only" << packet.contents.length() << "were given."; + return; + } - sendPacket("PN", {QString::number(server->player_count), max_players}); - sendPacket("FL", feature_list); - } - else if (packet.header == "askchaa") { - // TODO: add user configurable content - // For testing purposes, we will just send enough to get things working - sendPacket("SI", {QString::number(server->characters.length()), "0", QString::number(server->area_names.length() + server->music_list.length())}); - } - else if (packet.header == "RC") { - sendPacket("SC", server->characters); - } - else if (packet.header == "RM") { - sendPacket("SM", server->area_names + server->music_list); - } - else if (packet.header == "RD") { - server->player_count++; - area->player_count++; - joined = true; - server->updateCharsTaken(area); - fullArup(); // Give client all the area data - arup(ARUPType::PLAYER_COUNT, true); // Tell everyone there is a new player - - sendPacket("HP", {"1", QString::number(area->def_hp)}); - sendPacket("HP", {"2", QString::number(area->pro_hp)}); - sendPacket("FA", server->area_names); - sendPacket("OPPASS", {"DEADBEEF"}); - sendPacket("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 > server->characters.length()) - return; - - 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(area); - sendPacket("PV", {"271828", "CID", packet.contents[1]}); - } - else if (packet.header == "MS") { - // TODO: validate, validate, validate - ICChatPacket ic_packet(packet); - if (ic_packet.is_valid) - server->broadcast(ic_packet, current_area); - } - else if (packet.header == "CT") { - ooc_name = packet.contents[0]; - if(packet.contents[1].at(0) == '/') { - QStringList argv = packet.contents[1].split(" ", QString::SplitBehavior::SkipEmptyParts); - QString command = argv[0].trimmed().toLower(); - command = command.right(command.length() - 1); - argv.removeFirst(); - int argc = argv.length(); - handleCommand(command, argc, argv); - return; - } - // TODO: zalgo strip - server->broadcast(packet, current_area); - } - else if (packet.header == "CH") { - // Why does this packet exist - // At least Crystal made it useful - // It is now used for ping measurement - sendPacket("CHECK"); - } - else if (packet.header == "MC") { - // Due to historical reasons, this - // packet has two functions: - // Change area, and set music. - - // First, we check if the provided - // argument is a valid song - QString argument = packet.contents[0]; - - for (QString song : server->music_list) { - if (song == argument) { - // If we have a song, retransmit as-is - server->broadcast(packet, current_area); - return; - } - } - - for (int i = 0; i < server->area_names.length(); i++) { - QString area = server->area_names[i]; - if(area == argument) { - changeArea(i); - break; - } - } - } - else if (packet.header == "RT") { - if (QDateTime::currentDateTime().toSecsSinceEpoch() - last_wtce_time <= 5) - return; - last_wtce_time = QDateTime::currentDateTime().toSecsSinceEpoch(); - server->broadcast(packet, current_area); - } - else if (packet.header == "HP") { - if (packet.contents[0] == "1") { - area->def_hp = std::min(std::max(0, packet.contents[1].toInt()), 10); - } - else if (packet.contents[0] == "2") { - area->pro_hp = std::min(std::max(0, packet.contents[1].toInt()), 10); - } - server->broadcast(AOPacket("HP", {"1", QString::number(area->def_hp)}), area->index); - server->broadcast(AOPacket("HP", {"2", QString::number(area->pro_hp)}), area->index); - } - else if (packet.header == "WSIP") { - // Special packet to set remote IP from the webao proxy - // Only valid if from a local ip - if (remote_ip.isLoopback()) { - if(server->ban_manager->isIPBanned(QHostAddress(packet.contents[0]))) { - sendPacket("BD", {server->ban_manager->getBanReason(QHostAddress(packet.contents[0]))}); - socket->close(); - return; - } - qDebug() << "ws ip set to" << packet.contents[0]; - remote_ip = QHostAddress(packet.contents[0]); - } - } - else { - qDebug() << "Unimplemented packet:" << packet.header; - qDebug() << packet.contents; - } + (this->*(info.action))(area, packet.contents.length(), packet.contents, packet); } void AOClient::changeArea(int new_area) diff --git a/src/packets.cpp b/src/packets.cpp new file mode 100644 index 0000000..9cad840 --- /dev/null +++ b/src/packets.cpp @@ -0,0 +1,220 @@ +////////////////////////////////////////////////////////////////////////////////////// +// 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/aoclient.h" + +void AOClient::pktDefault(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + qDebug() << "Unimplemented packet:" << packet.header; + qDebug() << packet.contents; +} + +void AOClient::pktHardwareId(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + setHwid(argv[0]); + if(server->ban_manager->isHDIDBanned(getHwid())) { + sendPacket("BD", {server->ban_manager->getBanReason(getHwid())}); + socket->close(); + return; + } + sendPacket("ID", {"271828", "akashi", QCoreApplication::applicationVersion()}); +} + +void AOClient::pktSoftwareId(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + QSettings config("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_alerts", "modcall_reason", + "looping_sfx", "additive", "effects"}; + + sendPacket("PN", {QString::number(server->player_count), max_players}); + sendPacket("FL", feature_list); +} + +void AOClient::pktBeginLoad(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + // TODO: add user configurable content + // For testing purposes, we will just send enough to get things working + sendPacket("SI", {QString::number(server->characters.length()), "0", QString::number(server->area_names.length() + server->music_list.length())}); +} + +void AOClient::pktRequestChars(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + sendPacket("SC", server->characters); +} + +void AOClient::pktRequestMusic(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + sendPacket("SM", server->area_names + server->music_list); +} + +void AOClient::pktLoadingDone(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + server->player_count++; + area->player_count++; + joined = true; + server->updateCharsTaken(area); + fullArup(); // Give client all the area data + arup(ARUPType::PLAYER_COUNT, true); // Tell everyone there is a new player + + sendPacket("HP", {"1", QString::number(area->def_hp)}); + sendPacket("HP", {"2", QString::number(area->pro_hp)}); + sendPacket("FA", server->area_names); + sendPacket("OPPASS", {"DEADBEEF"}); + sendPacket("DONE"); +} + +void AOClient::pktCharPassword(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + password = argv[0]; +} + +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) + return; + + if (current_char != "") { + area->characters_taken[current_char] = false; + } + + if(char_id > server->characters.length()) + return; + + 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(area); + sendPacket("PV", {"271828", "CID", argv[1]}); +} + +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); +} + +void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + ooc_name = argv[0]; + if(argv[1].at(0) == '/') { + QStringList cmd_argv = argv[1].split(" ", QString::SplitBehavior::SkipEmptyParts); + QString command = cmd_argv[0].trimmed().toLower(); + command = command.right(command.length() - 1); + cmd_argv.removeFirst(); + int cmd_argc = argv.length(); + handleCommand(command, cmd_argc, cmd_argv); + return; + } + // TODO: zalgo strip + server->broadcast(packet, current_area); +} + +void AOClient::pktPing(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + // Why does this packet exist + // At least Crystal made it useful + // It is now used for ping measurement + sendPacket("CHECK"); +} + +void AOClient::pktChangeMusic(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + // Due to historical reasons, this + // packet has two functions: + // Change area, and set music. + + // First, we check if the provided + // argument is a valid song + QString argument = argv[0]; + + for (QString song : server->music_list) { + if (song == argument) { + // If we have a song, retransmit as-is + server->broadcast(packet, current_area); + return; + } + } + + for (int i = 0; i < server->area_names.length(); i++) { + QString area = server->area_names[i]; + if(area == argument) { + changeArea(i); + break; + } + } +} + +void AOClient::pktWtCe(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + if (QDateTime::currentDateTime().toSecsSinceEpoch() - last_wtce_time <= 5) + return; + last_wtce_time = QDateTime::currentDateTime().toSecsSinceEpoch(); + server->broadcast(packet, current_area); +} + +void AOClient::pktHpBar(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + if (argv[0] == "1") { + area->def_hp = std::min(std::max(0, argv[1].toInt()), 10); + } + else if (argv[0] == "2") { + area->pro_hp = std::min(std::max(0, argv[1].toInt()), 10); + } + server->broadcast(AOPacket("HP", {"1", QString::number(area->def_hp)}), area->index); + server->broadcast(AOPacket("HP", {"2", QString::number(area->pro_hp)}), area->index); +} + +void AOClient::pktWebSocketIp(AreaData* area, int argc, QStringList argv, AOPacket packet) +{ + // Special packet to set remote IP from the webao proxy + // Only valid if from a local ip + if (remote_ip.isLoopback()) { + if(server->ban_manager->isIPBanned(QHostAddress(argv[0]))) { + sendPacket("BD", {server->ban_manager->getBanReason(QHostAddress(argv[0]))}); + socket->close(); + return; + } + qDebug() << "ws ip set to" << argv[0]; + remote_ip = QHostAddress(argv[0]); + } +}