diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3ba2b3a..5a00e63 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -145,8 +145,8 @@ jobs: - name: Deploy OpenSSL run: | - curl https://mirror.firedaemon.com/OpenSSL/openssl-1.1.1o.zip --output openssl-1.1.1o.zip - tar -xf openssl-1.1.1o.zip + curl https://mirror.firedaemon.com/OpenSSL/openssl-1.1.1p.zip --output openssl-1.1.1p.zip + tar -xf openssl-1.1.1p.zip copy .\openssl-1.1\x64\bin\libcrypto-1_1-x64.dll .\bin\libcrypto-1_1-x64.dll copy .\openssl-1.1\x64\bin\libssl-1_1-x64.dll .\bin\libssl-1_1-x64.dll @@ -154,4 +154,4 @@ jobs: uses: actions/upload-artifact@v2 with: name: akashi-windows - path: bin\ \ No newline at end of file + path: bin\ diff --git a/akashi/akashi.pro b/akashi/akashi.pro index c7f2246..1d08e7b 100644 --- a/akashi/akashi.pro +++ b/akashi/akashi.pro @@ -2,7 +2,8 @@ QT += network websockets core sql QT -= gui TEMPLATE = app -CONFIG += c++11 console +unix:CONFIG += c++1z console +win32: CONFIG+=c++2a console coverage { LIBS += -lgcov diff --git a/core/core.pro b/core/core.pro index 0052b3a..c3bc115 100644 --- a/core/core.pro +++ b/core/core.pro @@ -6,7 +6,8 @@ TEMPLATE = lib # Apparently, Windows needs a static config to make a dynamic library? # Look, I dunno. # Linux works just fine with `shared` only. -CONFIG += shared static c++11 +unix: CONFIG += shared static c++1z +win32: CONFIG+= shared static c++2a coverage { QMAKE_CXXFLAGS += --coverage -g -Og # -fprofile-arcs -ftest-coverage @@ -28,7 +29,7 @@ DESTDIR = $$PWD/../bin #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 # Enable this to print network messages to the console -#DEFINES += NET_DEBUG +DEFINES += NET_DEBUG SOURCES += \ src/acl_roles_handler.cpp \ @@ -55,11 +56,34 @@ SOURCES += \ src/logger/u_logger.cpp \ src/logger/writer_modcall.cpp \ src/logger/writer_full.cpp \ - src/music_manager.cpp + src/music_manager.cpp \ + src/packet/packet_factory.cpp \ + src/packet/packet_generic.cpp \ + src/packet/packet_hi.cpp \ + src/packet/packet_id.cpp \ + src/packet/packet_askchaa.cpp \ + src/packet/packet_casea.cpp \ + src/packet/packet_cc.cpp \ + src/packet/packet_ch.cpp \ + src/packet/packet_ct.cpp \ + src/packet/packet_de.cpp \ + src/packet/packet_ee.cpp \ + src/packet/packet_hp.cpp \ + src/packet/packet_mc.cpp \ + src/packet/packet_ms.cpp \ + src/packet/packet_pe.cpp \ + src/packet/packet_pw.cpp \ + src/packet/packet_rc.cpp \ + src/packet/packet_rd.cpp \ + src/packet/packet_rm.cpp \ + src/packet/packet_rt.cpp \ + src/packet/packet_setcase.cpp \ + src/packet/packet_zz.cpp HEADERS += include/aoclient.h \ include/acl_roles_handler.h \ include/akashidefs.h \ + include/akashiutils.h \ include/network/aopacket.h \ include/network/network_socket.h \ include/area_data.h \ @@ -74,4 +98,27 @@ HEADERS += include/aoclient.h \ include/logger/u_logger.h \ include/logger/writer_modcall.h \ include/logger/writer_full.h \ - include/music_manager.h + include/music_manager.h \ + include/packet/packet_factory.h \ + include/packet/packet_info.h \ + include/packet/packet_generic.h \ + include/packet/packet_hi.h \ + include/packet/packet_id.h \ + include/packet/packet_askchaa.h \ + include/packet/packet_casea.h \ + include/packet/packet_cc.h \ + include/packet/packet_ch.h \ + include/packet/packet_ct.h \ + include/packet/packet_de.h \ + include/packet/packet_ee.h \ + include/packet/packet_hp.h \ + include/packet/packet_mc.h \ + include/packet/packet_ms.h \ + include/packet/packet_pe.h \ + include/packet/packet_pw.h \ + include/packet/packet_rc.h \ + include/packet/packet_rd.h \ + include/packet/packet_rm.h \ + include/packet/packet_rt.h \ + include/packet/packet_setcase.h \ + include/packet/packet_zz.h diff --git a/core/include/akashiutils.h b/core/include/akashiutils.h new file mode 100644 index 0000000..fa35939 --- /dev/null +++ b/core/include/akashiutils.h @@ -0,0 +1,57 @@ +////////////////////////////////////////////////////////////////////////////////////// +// 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 AKASHI_UTILS_H +#define AKASHI_UTILS_H + +#include +#include + +class AkashiUtils +{ + private: + AkashiUtils(){}; + + public: + template + static inline bool checkArgType(QString arg) + { + QVariant qvar = arg; + if (!qvar.canConvert()) + return false; + + if (std::is_same()) { + bool ok; + qvar.toInt(&ok); + return ok; + } + else if (std::is_same()) { + bool ok; + float f = qvar.toFloat(&ok); + return ok && !isnan((float)f); + } + else if (std::is_same()) { + bool ok; + double d = qvar.toDouble(&ok); + return ok && !isnan((double)d); + } + + return true; + }; +}; + +#endif // AKASHI_UTILS_H diff --git a/core/include/aoclient.h b/core/include/aoclient.h index 242838b..88cf447 100644 --- a/core/include/aoclient.h +++ b/core/include/aoclient.h @@ -34,6 +34,8 @@ class AreaData; class DBManager; class MusicManager; class Server; +class NetworkSocket; +class AOPacket; /** * @brief Represents a client connected to the server running Attorney Online 2 or one of its derivatives. @@ -318,6 +320,37 @@ class AOClient : public QObject */ bool m_is_spectator = true; + /** + * @brief The hardware ID of the client. + * + * @details Generated based on the client's own supplied hardware ID. + * The client supplied hardware ID is generally a machine unique ID. + */ + QString m_hwid; + + /** + * @brief The network socket used by the client. Can either be a Websocket or TCP Socket. + */ + NetworkSocket *m_socket; + + /** + * @brief The IPID of the client. + * + * @details Generated based on the client's IP, but cannot be reversed to identify the client's IP. + */ + QString m_ipid; + + /** + * @brief The type of area update, used for area update (ARUP) packets. + */ + enum ARUPType + { + PLAYER_COUNT, //!< The packet contains player count updates. + STATUS, //!< The packet contains area status updates. + CM, //!< The packet contains updates about who's the CM of what area. + LOCKED //!< The packet contains updates about what areas are locked. + }; + /** * @brief Checks if the client's ACL role has permission for the given permission. * @@ -341,109 +374,6 @@ class AOClient : public QObject */ void setSpectator(bool f_spectator); - /** - * @brief The spectator character ID - * - * @details You may assume that AO has a sane way to determine if a user is a spectator - * or an actual player. Well, to nobodys surprise, this is not the case, so the character id -1 is used - * to determine if a client has entered spectator or user mode. I am making this a const mostly - * for the case this could change at some point in the future, but don't count on it. - */ - const int SPECTATOR_ID = -1; - - public slots: - /** - * @brief Handles an incoming packet, checking for authorisation and minimum argument count. - * - * @param packet The incoming packet. - */ - void handlePacket(AOPacket packet); - - /** - * @brief A slot for when the client disconnects from the server. - */ - void clientDisconnected(); - - /** - * @brief A slot for sending a packet to the client. - * - * @param packet The packet to send. - */ - void sendPacket(AOPacket packet); - - /** - * @overload - */ - void sendPacket(QString header, QStringList contents); - - /** - * @overload - */ - void sendPacket(QString header); - - /** - * @brief A slot for when the client's AFK timer runs out. - */ - void onAfkTimeout(); - - signals: - /** - * @brief This signal is emitted when the client has completed the participation handshake. - */ - void joined(); - - private: - /** - * @brief The network socket used by the client. Can either be a Websocket or TCP Socket. - */ - NetworkSocket *m_socket; - - /** - * @brief A pointer to the Server, used for updating server variables that depend on the client (e.g. amount of players in an area). - */ - Server *server; - - /** - * @brief The type of area update, used for area update (ARUP) packets. - */ - enum ARUPType - { - PLAYER_COUNT, //!< The packet contains player count updates. - STATUS, //!< The packet contains area status updates. - CM, //!< The packet contains updates about who's the CM of what area. - LOCKED //!< The packet contains updates about what areas are locked. - }; - - /** - * @brief Handles an incoming command, checking for authorisation and minimum argument count. - * - * @param command The incoming command. - * @param argc The amount of arguments the command was called with. Equivalent to `argv.size()`. - * @param argv The arguments the command was called with. - */ - void handleCommand(QString command, int argc, QStringList argv); - - /** - * @brief Changes the area the client is in. - * - * @param new_area The ID of the new area. - */ - void changeArea(int new_area); - - /** - * @brief Changes the client's character. - * - * @param char_id The character ID of the client's new character. - */ - bool changeCharacter(int char_id); - - /** - * @brief Changes the client's in-character position. - * - * @param new_pos The new position of the client. - */ - void changePosition(QString new_pos); - /** * @brief Sends or announces an ARUP update. * @@ -479,114 +409,12 @@ class AOClient : public QObject */ void sendServerBroadcast(QString message); - /** - * @name Packet headers - * - * @details These functions implement the AO2-style packet handling. - * As these should generally be the same across server software, I see no reason to document them specifically. - * - * You can check out the AO2 network protocol for explanations. - * - * All packet handling functions share the same parameters: - * - * @param area The area the client is in. Some packets make use of the client's current area. - * @param argc The amount of arguments in the packet, not counting the header. Same as `argv.size()`. - * @param argv The arguments in the packet, once again, not counting the header. - * @param packet The... arguments in the packet. Yes, exactly the same as `argv`, just packed into an AOPacket. - * - * @see https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md for the AO2 network protocol. - */ - ///@{ - - /// A "default" packet handler, to be used for error checking and copying other packet handlers. - void pktDefault(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [hardware ID](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#hard-drive-id). - void pktHardwareId(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /** - * @brief Implements [feature list](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#feature-list) and - * [player count](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#player-count). - */ - void pktSoftwareId(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [resource counts](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#resource-counts). - void pktBeginLoad(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [character list](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#character-list). - void pktRequestChars(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [music list](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#music-list). - void pktRequestMusic(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [the final loading confirmation](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#final-confirmation). - void pktLoadingDone(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /** - * @brief Implements character passwording. This is not on the netcode documentation as of writing. - * - * @todo Link packet details when it gets into the netcode documentation. - */ - void pktCharPassword(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [character selection](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#choose-character). - void pktSelectChar(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [the in-character messaging hell](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#in-character-message). - void pktIcChat(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [out-of-character messages](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#out-of-character-message). - void pktOocChat(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [the keepalive packet](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#keep-alive). - void pktPing(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /** - * @brief Implements [music](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#music) and - * [area changing](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#switch-area). - */ - void pktChangeMusic(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /** - * @brief Implements [the witness testimony / cross examination / judge decision popups] - * (https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#witness-testimonycross-examination-wtce). - */ - void pktWtCe(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [penalty bars](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#penalty-health-bars). - void pktHpBar(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [moderator calling](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#call-mod). - void pktModCall(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [adding evidence](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#add). - void pktAddEvidence(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [removing evidence](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#remove). - void pktRemoveEvidence(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [editing evidence](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#edit). - void pktEditEvidence(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [updating casing preferences](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#case-preferences-update). - void pktSetCase(AreaData *area, int argc, QStringList argv, AOPacket packet); - - /// Implements [announcing a case](https://github.com/AttorneyOnline/docs/blob/master/docs/development/network.md#case-alert). - void pktAnnounceCase(AreaData *area, int argc, QStringList argv, AOPacket packet); - - ///@} - - /** - * @name Packet helper functions - */ - ///@{ - /** * @brief Calls AOClient::updateEvidenceList() for every client in the current client's area. * * @param area The current client's area. */ - void sendEvidenceList(AreaData *area); + void sendEvidenceList(AreaData *area) const; /** * @brief Updates the evidence list in the area for the client. @@ -595,15 +423,6 @@ class AOClient : public QObject */ void updateEvidenceList(AreaData *area); - /** - * @brief Attempts to validate that hellish abomination that Attorney Online 2 calls an in-character packet. - * - * @param packet The packet to validate. - * - * @return A validated version of the input packet if it is correct, or an `"INVALID"` packet if it is not. - */ - AOPacket validateIcPacket(AOPacket packet); - /** * @brief Removes excessive combining characters from a text. * @@ -624,7 +443,101 @@ class AOClient : public QObject */ bool checkEvidenceAccess(AreaData *area); - ///@} + /** + * @brief Changes the client's character. + * + * @param char_id The character ID of the client's new character. + */ + bool changeCharacter(int char_id); + + /** + * @brief A helper function for logging in a client as moderator. + * + * @param message The OOC message the client has sent. + */ + void loginAttempt(QString message); + + /** + * @brief Changes the area the client is in. + * + * @param new_area The ID of the new area. + */ + void changeArea(int new_area); + + /** + * @brief Handles an incoming command, checking for authorisation and minimum argument count. + * + * @param command The incoming command. + * @param argc The amount of arguments the command was called with. Equivalent to `argv.size()`. + * @param argv The arguments the command was called with. + */ + void handleCommand(QString command, int argc, QStringList argv); + + /** + * @brief A helper function for decoding AO encoding from a QString. + * + * @param incoming_message QString to be decoded. + */ + QString decodeMessage(QString incoming_message); + + /** + * @brief Adds the last send IC-Message to QVector of the respective area. + * + * @details This one pulls double duty to both append IC-Messages to the QVector or insert them, depending on the current recorder enum. + * + * @param packet The MS-Packet being recorded with their color changed to green. + */ + void addStatement(QStringList packet); + + /** + * @brief Updates the currently displayed IC-Message with the next one send + * @param packet The IC-Message that will overwrite the currently stored one. + * @return Returns the updated IC-Message to be send to the other users. It also changes the color to green. + */ + QStringList updateStatement(QStringList packet); + + /** + * @brief Convenience function to generate a random integer number between the given minimum and maximum values. + * + * @param min The minimum possible value for the random integer, inclusive. + * @param max The maximum possible value for the random integer, exclusive. + * + * @warning `max` must be greater than `min`. + * + * @return A randomly generated integer within the bounds given. + */ + int genRand(int min, int max); + + /** + * @brief A helper function to add recorded packets to an area's judgelog. + * + * @param area Pointer to the area where the packet was sent. + * + * @param client Pointer to the client that sent the packet. + * + * @param action String containing the info that is being recorded. + */ + void updateJudgeLog(AreaData *area, AOClient *client, QString action); + + /** + * @brief Pointer to the servers music manager instance. + */ + MusicManager *m_music_manager; + + /** + * @brief The text of the last in-character message that was sent by the client. + * + * @details Used to determine if the incoming message is a duplicate. + */ + QString m_last_message; + + /** + * @brief The time in seconds since the client last sent a Witness Testimony / Cross Examination + * popup packet. + * + * @details Used to filter out potential spam. + */ + long m_last_wtce_time; /** * @name Packet helper global variables @@ -641,15 +554,6 @@ class AOClient : public QObject */ QString m_acl_role_id; - /** - * @brief The client's character ID. - * - * @details A character ID is just the character's index in the server's character list. - * - * In general, the client assumes that this is a continuous block starting from 0. - */ - int m_char_id = -1; - /** * @brief The character ID of the other character that the client wants to pair up with. * @@ -687,13 +591,85 @@ class AOClient : public QObject ///@} - /// Describes a packet's interpretation details. - struct PacketInfo - { - ACLRole::Permission acl_permission; //!< The permissions necessary for the packet. - int minArgs; //!< The minimum arguments needed for the packet to be interpreted correctly / make sense. - void (AOClient::*action)(AreaData *, int, QStringList, AOPacket); - }; + /** + * @brief The client's character ID. + * + * @details A character ID is just the character's index in the server's character list. + * + * In general, the client assumes that this is a continuous block starting from 0. + */ + int m_char_id = -1; + + /** + * @brief The spectator character ID + * + * @details You may assume that AO has a sane way to determine if a user is a spectator + * or an actual player. Well, to nobodys surprise, this is not the case, so the character id -1 is used + * to determine if a client has entered spectator or user mode. I am making this a const mostly + * for the case this could change at some point in the future, but don't count on it. + */ + const int SPECTATOR_ID = -1; + + public slots: + /** + * @brief Handles an incoming packet, checking for authorisation and minimum argument count. + * + * @param packet The incoming packet. + */ + void handlePacket(AOPacket *packet); + + /** + * @brief A slot for when the client disconnects from the server. + */ + void clientDisconnected(); + + /** + * @brief A slot for sending a packet to the client. + * + * @param packet The packet to send. + */ + void sendPacket(AOPacket *packet); + + /** + * @overload + */ + void sendPacket(QString header, QStringList contents); + + /** + * @overload + */ + void sendPacket(QString header); + + /** + * @brief A slot for when the client's AFK timer runs out. + */ + void onAfkTimeout(); + + signals: + /** + * @brief This signal is emitted when the client has completed the participation handshake. + */ + void joined(); + + private: + /** + * @brief A pointer to the Server, used for updating server variables that depend on the client (e.g. amount of players in an area). + */ + Server *server; + + /** + * @brief Changes the client's in-character position. + * + * @param new_pos The new position of the client. + */ + void changePosition(QString new_pos); + + /** + * @name Packet helper functions + */ + ///@{ + + ///@} /** * @property PacketInfo::action @@ -706,38 +682,6 @@ class AOClient : public QObject * @param AOPacket This is a duplicated version of the QStringList above, containing the same data. */ - /** - * @brief The list of packets that the server can interpret. - * - * @showinitializer - * - * @tparam QString The header of the packet that uniquely identifies it. - * @tparam PacketInfo The details of the packet. - * See @ref PacketInfo "the type's documentation" for more details. - */ - const QMap packets{ - {"HI", {ACLRole::NONE, 1, &AOClient::pktHardwareId}}, - {"ID", {ACLRole::NONE, 2, &AOClient::pktSoftwareId}}, - {"askchaa", {ACLRole::NONE, 0, &AOClient::pktBeginLoad}}, - {"RC", {ACLRole::NONE, 0, &AOClient::pktRequestChars}}, - {"RM", {ACLRole::NONE, 0, &AOClient::pktRequestMusic}}, - {"RD", {ACLRole::NONE, 0, &AOClient::pktLoadingDone}}, - {"PW", {ACLRole::NONE, 1, &AOClient::pktCharPassword}}, - {"CC", {ACLRole::NONE, 3, &AOClient::pktSelectChar}}, - {"MS", {ACLRole::NONE, 15, &AOClient::pktIcChat}}, - {"CT", {ACLRole::NONE, 2, &AOClient::pktOocChat}}, - {"CH", {ACLRole::NONE, 1, &AOClient::pktPing}}, - {"MC", {ACLRole::NONE, 2, &AOClient::pktChangeMusic}}, - {"RT", {ACLRole::NONE, 1, &AOClient::pktWtCe}}, - {"HP", {ACLRole::NONE, 2, &AOClient::pktHpBar}}, - {"ZZ", {ACLRole::NONE, 0, &AOClient::pktModCall}}, - {"PE", {ACLRole::NONE, 3, &AOClient::pktAddEvidence}}, - {"DE", {ACLRole::NONE, 1, &AOClient::pktRemoveEvidence}}, - {"EE", {ACLRole::NONE, 4, &AOClient::pktEditEvidence}}, - {"SETCASE", {ACLRole::NONE, 7, &AOClient::pktSetCase}}, - {"CASEA", {ACLRole::NONE, 6, &AOClient::pktAnnounceCase}}, - }; - /** * @name Authentication */ @@ -2018,18 +1962,6 @@ class AOClient : public QObject */ QStringList buildAreaList(int area_idx); - /** - * @brief Convenience function to generate a random integer number between the given minimum and maximum values. - * - * @param min The minimum possible value for the random integer, inclusive. - * @param max The maximum possible value for the random integer, exclusive. - * - * @warning `max` must be greater than `min`. - * - * @return A randomly generated integer within the bounds given. - */ - int genRand(int min, int max); - /** * @brief A convenience function for rolling dice. * @@ -2066,15 +1998,6 @@ class AOClient : public QObject long long parseTime(QString input); QString getReprimand(bool f_positive = false); - /** - * @brief Adds the last send IC-Message to QVector of the respective area. - * - * @details This one pulls double duty to both append IC-Messages to the QVector or insert them, depending on the current recorder enum. - * - * @param packet The MS-Packet being recorded with their color changed to green. - */ - void addStatement(QStringList packet); - /** * @brief Clears QVector of the current area. * @@ -2083,13 +2006,6 @@ class AOClient : public QObject */ void clearTestimony(); - /** - * @brief Updates the currently displayed IC-Message with the next one send - * @param packet The IC-Message that will overwrite the currently stored one. - * @return Returns the updated IC-Message to be send to the other users. It also changes the color to green. - */ - QStringList updateStatement(QStringList packet); - /** * @brief Called when area enum is set to PLAYBACK. Sends the IC-Message stored at the current statement. * @return IC-Message stored in the QVector. @@ -2150,71 +2066,11 @@ class AOClient : public QObject */ bool is_partial; - /** - * @brief The hardware ID of the client. - * - * @details Generated based on the client's own supplied hardware ID. - * The client supplied hardware ID is generally a machine unique ID. - */ - QString m_hwid; - - /** - * @brief The IPID of the client. - * - * @details Generated based on the client's IP, but cannot be reversed to identify the client's IP. - */ - QString m_ipid; - - /** - * @brief The time in seconds since the client last sent a Witness Testimony / Cross Examination - * popup packet. - * - * @details Used to filter out potential spam. - */ - long m_last_wtce_time; - - /** - * @brief The text of the last in-character message that was sent by the client. - * - * @details Used to determine if the incoming message is a duplicate. - */ - QString m_last_message; - - /** - * @brief Pointer to the servers music manager instance. - */ - MusicManager *m_music_manager; - - /** - * @brief A helper function to add recorded packets to an area's judgelog. - * - * @param area Pointer to the area where the packet was sent. - * - * @param client Pointer to the client that sent the packet. - * - * @param action String containing the info that is being recorded. - */ - void updateJudgeLog(AreaData *area, AOClient *client, QString action); - - /** - * @brief A helper function for decoding AO encoding from a QString. - * - * @param incoming_message QString to be decoded. - */ - QString decodeMessage(QString incoming_message); - /** * @brief The size, in bytes, of the last data the client sent to the server. */ int last_read = 0; - /** - * @brief A helper function for logging in a client as moderator. - * - * @param message The OOC message the client has sent. - */ - void loginAttempt(QString message); - signals: /** diff --git a/core/include/area_data.h b/core/include/area_data.h index 902ee8f..87f10d7 100644 --- a/core/include/area_data.h +++ b/core/include/area_data.h @@ -31,6 +31,7 @@ class ConfigManager; class Logger; class MusicManager; +class AOPacket; /** * @brief Represents an area on the server, a distinct "room" for people to chat in. @@ -927,7 +928,7 @@ class AreaData : public QObject /** * @brief Sends a packet to every client inside the area. */ - void sendAreaPacket(AOPacket f_packet, int f_area_index); + void sendAreaPacket(AOPacket *f_packet, int f_area_index); /** * @brief sendAreaPacketClient Sends a packet to the specified client. @@ -936,7 +937,7 @@ class AreaData : public QObject * * @param f_user_id The user ID of the client. */ - void sendAreaPacketClient(AOPacket f_packet, int f_user_id); + void sendAreaPacketClient(AOPacket *f_packet, int f_user_id); /** * @brief userJoinedArea Signals that a new client has joined an area. diff --git a/core/include/music_manager.h b/core/include/music_manager.h index a1b21d9..44379c4 100644 --- a/core/include/music_manager.h +++ b/core/include/music_manager.h @@ -168,7 +168,7 @@ class MusicManager : public QObject * * @param f_user_id temporary userid of the incoming client. */ - void sendFMPacket(AOPacket f_packet, int f_user_id); + void sendFMPacket(AOPacket *f_packet, int f_user_id); /** * @brief Sends the FM packet with the musiclist of the area when changes are made. @@ -177,7 +177,7 @@ class MusicManager : public QObject * * @param f_area_index Index of the current area the edit is made in. */ - void sendAreaFMPacket(AOPacket f_packet, int f_area_index); + void sendAreaFMPacket(AOPacket *f_packet, int f_area_index); private: /** diff --git a/core/include/network/aopacket.h b/core/include/network/aopacket.h index 3d0404e..7027e58 100644 --- a/core/include/network/aopacket.h +++ b/core/include/network/aopacket.h @@ -23,6 +23,12 @@ #include #include +#include "include/aoclient.h" +#include "include/area_data.h" +#include "include/packet/packet_info.h" + +class AOClient; + /** * @brief An Attorney Online 2 compatible packet. * @@ -32,20 +38,7 @@ class AOPacket { public: - /** - * @brief Creates an AOPacket with the given header and contents. - * - * @param p_header The header for the packet. - * @param p_contents The contents of the packet. - */ - AOPacket(QString p_header, QStringList p_contents); - - /** - * @brief Create an AOPacket from an incoming network message. - * - * @param f_packet An escaped string with header and content. - */ - AOPacket(QString f_packet); + AOPacket(QStringList p_contents); /** * @brief Destructor for the AOPacket @@ -59,13 +52,6 @@ class AOPacket */ const QStringList getContent(); - /** - * @brief Returns the header of the packet. - * - * @return The packets header. - */ - QString getHeader(); - /** * @brief Converts the header and content into a single string. * @@ -125,15 +111,13 @@ class AOPacket */ bool isPacketEscaped(); - private: - /** - * @brief The header of the packet. - * - * @see https://github.com/AttorneyOnline/docs/blob/master/AO%20Documentation/docs/development/network.md#network-protocol - * for a general explanation on Attorney Online 2's network protocl. - */ - QString m_header; + virtual PacketInfo getPacketInfo() const = 0; + virtual void handlePacket(AreaData *area, AOClient &client) const = 0; + virtual bool validatePacket() const = 0; + static void registerPackets(); + + protected: /** * @brief The contents of the packet. */ diff --git a/core/include/network/network_socket.h b/core/include/network/network_socket.h index 1b7213f..f3c463c 100644 --- a/core/include/network/network_socket.h +++ b/core/include/network/network_socket.h @@ -25,6 +25,8 @@ #include "include/network/aopacket.h" +class AOPacket; + class NetworkSocket : public QObject { Q_OBJECT @@ -73,7 +75,7 @@ class NetworkSocket : public QObject * * @param Packet to be written to the socket. */ - void write(AOPacket f_packet); + void write(AOPacket *f_packet); signals: @@ -81,7 +83,7 @@ class NetworkSocket : public QObject * @brief handlePacket * @param f_packet */ - void handlePacket(AOPacket f_packet); + void handlePacket(AOPacket *f_packet); /** * @brief Emitted when the socket has been closed and the client is disconnected. diff --git a/core/include/packet/packet_askchaa.h b/core/include/packet/packet_askchaa.h new file mode 100644 index 0000000..8db348f --- /dev/null +++ b/core/include/packet/packet_askchaa.h @@ -0,0 +1,14 @@ +#ifndef PACKET_ASKCHAA_H +#define PACKET_ASKCHAA_H + +#include "include/network/aopacket.h" + +class PacketAskchaa : public AOPacket +{ + public: + PacketAskchaa(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_casea.h b/core/include/packet/packet_casea.h new file mode 100644 index 0000000..0212917 --- /dev/null +++ b/core/include/packet/packet_casea.h @@ -0,0 +1,14 @@ +#ifndef PACKET_CASEA_H +#define PACKET_CASEA_H + +#include "include/network/aopacket.h" + +class PacketCasea : public AOPacket +{ + public: + PacketCasea(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_cc.h b/core/include/packet/packet_cc.h new file mode 100644 index 0000000..51342ca --- /dev/null +++ b/core/include/packet/packet_cc.h @@ -0,0 +1,14 @@ +#ifndef PACKET_CC_H +#define PACKET_CC_H + +#include "include/network/aopacket.h" + +class PacketCC : public AOPacket +{ + public: + PacketCC(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_ch.h b/core/include/packet/packet_ch.h new file mode 100644 index 0000000..815b802 --- /dev/null +++ b/core/include/packet/packet_ch.h @@ -0,0 +1,14 @@ +#ifndef PACKET_CH_H +#define PACKET_CH_H + +#include "include/network/aopacket.h" + +class PacketCH : public AOPacket +{ + public: + PacketCH(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_ct.h b/core/include/packet/packet_ct.h new file mode 100644 index 0000000..e1ffa5a --- /dev/null +++ b/core/include/packet/packet_ct.h @@ -0,0 +1,14 @@ +#ifndef PACKET_CT_H +#define PACKET_CT_H + +#include "include/network/aopacket.h" + +class PacketCT : public AOPacket +{ + public: + PacketCT(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_de.h b/core/include/packet/packet_de.h new file mode 100644 index 0000000..9dc2739 --- /dev/null +++ b/core/include/packet/packet_de.h @@ -0,0 +1,14 @@ +#ifndef PACKET_DE_H +#define PACKET_DE_H + +#include "include/network/aopacket.h" + +class PacketDE : public AOPacket +{ + public: + PacketDE(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_ee.h b/core/include/packet/packet_ee.h new file mode 100644 index 0000000..71d2aa5 --- /dev/null +++ b/core/include/packet/packet_ee.h @@ -0,0 +1,14 @@ +#ifndef PACKET_EE_H +#define PACKET_EE_H + +#include "include/network/aopacket.h" + +class PacketEE : public AOPacket +{ + public: + PacketEE(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_factory.h b/core/include/packet/packet_factory.h new file mode 100644 index 0000000..3ef76d6 --- /dev/null +++ b/core/include/packet/packet_factory.h @@ -0,0 +1,20 @@ +#include "include/network/aopacket.h" + +class PacketFactory +{ + public: + // thingy here to register/map strings to constructors + static AOPacket *createPacket(QString header, QStringList contents); + static AOPacket *createPacket(QString raw_packet); + template + static void registerClass(QString header) { class_map[header] = &createInstance; }; + + private: + template + static AOPacket *createInstance(QStringList contents) { return new T(contents); }; + template + static AOPacket *createInstance(QString header, QStringList contents) { return new T(header, contents); }; + typedef std::map type_map; + + static inline type_map class_map; +}; diff --git a/core/include/packet/packet_generic.h b/core/include/packet/packet_generic.h new file mode 100644 index 0000000..d7ac4db --- /dev/null +++ b/core/include/packet/packet_generic.h @@ -0,0 +1,17 @@ +#ifndef PACKET_GENERIC_H +#define PACKET_GENERIC_H + +#include "include/network/aopacket.h" + +class PacketGeneric : public AOPacket +{ + public: + PacketGeneric(QString header, QStringList contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; + + private: + QString header; +}; +#endif diff --git a/core/include/packet/packet_hi.h b/core/include/packet/packet_hi.h new file mode 100644 index 0000000..01291bd --- /dev/null +++ b/core/include/packet/packet_hi.h @@ -0,0 +1,17 @@ +#ifndef PACKET_HI_H +#define PACKET_HI_H + +#include "include/network/aopacket.h" + +class PacketHI : public AOPacket +{ + public: + PacketHI(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; + + private: + QString header; +}; +#endif diff --git a/core/include/packet/packet_hp.h b/core/include/packet/packet_hp.h new file mode 100644 index 0000000..21b8ccd --- /dev/null +++ b/core/include/packet/packet_hp.h @@ -0,0 +1,14 @@ +#ifndef PACKET_HP_H +#define PACKET_HP_H + +#include "include/network/aopacket.h" + +class PacketHP : public AOPacket +{ + public: + PacketHP(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_id.h b/core/include/packet/packet_id.h new file mode 100644 index 0000000..bb50547 --- /dev/null +++ b/core/include/packet/packet_id.h @@ -0,0 +1,14 @@ +#ifndef PACKET_ID_H +#define PACKET_ID_H + +#include "include/network/aopacket.h" + +class PacketID : public AOPacket +{ + public: + PacketID(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_info.h b/core/include/packet/packet_info.h new file mode 100644 index 0000000..9581b24 --- /dev/null +++ b/core/include/packet/packet_info.h @@ -0,0 +1,14 @@ +#ifndef PACKET_INFO_H +#define PACKET_INFO_H + +#include "include/acl_roles_handler.h" + +/// Describes a packet's interpretation details. +class PacketInfo +{ + public: + ACLRole::Permission acl_permission; //!< The permissions necessary for the packet. + int min_args; //!< The minimum arguments needed for the packet to be interpreted correctly / make sense. + QString header; +}; +#endif diff --git a/core/include/packet/packet_mc.h b/core/include/packet/packet_mc.h new file mode 100644 index 0000000..d3c271c --- /dev/null +++ b/core/include/packet/packet_mc.h @@ -0,0 +1,14 @@ +#ifndef PACKET_MC_H +#define PACKET_MC_H + +#include "include/network/aopacket.h" + +class PacketMC : public AOPacket +{ + public: + PacketMC(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_ms.h b/core/include/packet/packet_ms.h new file mode 100644 index 0000000..4086b60 --- /dev/null +++ b/core/include/packet/packet_ms.h @@ -0,0 +1,17 @@ +#ifndef PACKET_MS_H +#define PACKET_MS_H + +#include "include/network/aopacket.h" + +class PacketMS : public AOPacket +{ + public: + PacketMS(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; + + private: + AOPacket *validateIcPacket(AOClient &client) const; +}; +#endif diff --git a/core/include/packet/packet_pe.h b/core/include/packet/packet_pe.h new file mode 100644 index 0000000..f35dc7b --- /dev/null +++ b/core/include/packet/packet_pe.h @@ -0,0 +1,14 @@ +#ifndef PACKET_PE_H +#define PACKET_PE_H + +#include "include/network/aopacket.h" + +class PacketPE : public AOPacket +{ + public: + PacketPE(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_pw.h b/core/include/packet/packet_pw.h new file mode 100644 index 0000000..7a9ddba --- /dev/null +++ b/core/include/packet/packet_pw.h @@ -0,0 +1,14 @@ +#ifndef PACKET_PW_H +#define PACKET_PW_H + +#include "include/network/aopacket.h" + +class PacketPW : public AOPacket +{ + public: + PacketPW(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_rc.h b/core/include/packet/packet_rc.h new file mode 100644 index 0000000..f7fb1bf --- /dev/null +++ b/core/include/packet/packet_rc.h @@ -0,0 +1,14 @@ +#ifndef PACKET_RC_H +#define PACKET_RC_H + +#include "include/network/aopacket.h" + +class PacketRC : public AOPacket +{ + public: + PacketRC(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_rd.h b/core/include/packet/packet_rd.h new file mode 100644 index 0000000..4f51b87 --- /dev/null +++ b/core/include/packet/packet_rd.h @@ -0,0 +1,14 @@ +#ifndef PACKET_RD_H +#define PACKET_RD_H + +#include "include/network/aopacket.h" + +class PacketRD : public AOPacket +{ + public: + PacketRD(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_rm.h b/core/include/packet/packet_rm.h new file mode 100644 index 0000000..91b4b8e --- /dev/null +++ b/core/include/packet/packet_rm.h @@ -0,0 +1,14 @@ +#ifndef PACKET_RM_H +#define PACKET_RM_H + +#include "include/network/aopacket.h" + +class PacketRM : public AOPacket +{ + public: + PacketRM(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_rt.h b/core/include/packet/packet_rt.h new file mode 100644 index 0000000..604fd8d --- /dev/null +++ b/core/include/packet/packet_rt.h @@ -0,0 +1,14 @@ +#ifndef PACKET_RT_H +#define PACKET_RT_H + +#include "include/network/aopacket.h" + +class PacketRT : public AOPacket +{ + public: + PacketRT(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_setcase.h b/core/include/packet/packet_setcase.h new file mode 100644 index 0000000..1df3b13 --- /dev/null +++ b/core/include/packet/packet_setcase.h @@ -0,0 +1,14 @@ +#ifndef PACKET_SETCASE_H +#define PACKET_SETCASE_H + +#include "include/network/aopacket.h" + +class PacketSetcase : public AOPacket +{ + public: + PacketSetcase(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/packet/packet_zz.h b/core/include/packet/packet_zz.h new file mode 100644 index 0000000..8920863 --- /dev/null +++ b/core/include/packet/packet_zz.h @@ -0,0 +1,14 @@ +#ifndef PACKET_ZZ_H +#define PACKET_ZZ_H + +#include "include/network/aopacket.h" + +class PacketZZ : public AOPacket +{ + public: + PacketZZ(QStringList &contents); + virtual PacketInfo getPacketInfo() const; + virtual void handlePacket(AreaData *area, AOClient &client) const; + virtual bool validatePacket() const; +}; +#endif diff --git a/core/include/server.h b/core/include/server.h index 7b690e6..9485441 100644 --- a/core/include/server.h +++ b/core/include/server.h @@ -182,14 +182,14 @@ class Server : public QObject * * @note Does nothing if an area by the given index does not exist. */ - void broadcast(AOPacket packet, int area_index); + void broadcast(AOPacket *packet, int area_index); /** * @brief Sends a packet to all clients in the server. * * @param packet The packet to send to the clients. */ - void broadcast(AOPacket packet); + void broadcast(AOPacket *packet); /** * @brief Sends a packet to a specific usergroup.. @@ -198,7 +198,7 @@ class Server : public QObject * * @param ENUM to determine the targets of the altered packet. */ - void broadcast(AOPacket packet, TARGET_TYPE target); + void broadcast(AOPacket *packet, TARGET_TYPE target); /** * @brief Sends a packet to clients, sends an altered packet to a specific usergroup. @@ -209,7 +209,7 @@ class Server : public QObject * * @param ENUM to determine the targets of the altered packet. */ - void broadcast(AOPacket packet, AOPacket other_packet, enum TARGET_TYPE target); + void broadcast(AOPacket *packet, AOPacket *other_packet, enum TARGET_TYPE target); /** * @brief Sends a packet to a single client. @@ -218,7 +218,7 @@ class Server : public QObject * * @param The temporary userID of the client. */ - void unicast(AOPacket f_packet, int f_client_id); + void unicast(AOPacket *f_packet, int f_client_id); /** * @brief Returns the character's character ID (= their index in the character list). diff --git a/core/src/aoclient.cpp b/core/src/aoclient.cpp index 9866b55..8ac9e60 100644 --- a/core/src/aoclient.cpp +++ b/core/src/aoclient.cpp @@ -21,7 +21,7 @@ #include "include/command_extension.h" #include "include/config_manager.h" #include "include/db_manager.h" -#include "include/network/aopacket.h" +#include "include/packet/packet_factory.h" #include "include/server.h" const QMap AOClient::COMMANDS{ @@ -176,37 +176,36 @@ void AOClient::clientDisconnected() emit clientSuccessfullyDisconnected(m_id); } -void AOClient::handlePacket(AOPacket packet) +void AOClient::handlePacket(AOPacket *packet) { #ifdef NET_DEBUG - qDebug() << "Received packet:" << packet.getHeader() << ":" << packet.getContent() << "args length:" << packet.getContent().length(); + qDebug() << "Received packet:" << packet->getPacketInfo().header << ":" << packet->getContent() << "args length:" << packet->getContent().length(); #endif AreaData *l_area = server->getAreaById(m_current_area); - PacketInfo l_info = packets.value(packet.getHeader(), {ACLRole::NONE, 0, &AOClient::pktDefault}); - if (packet.getContent().join("").size() > 16384) { + if (packet->getContent().join("").size() > 16384) { return; } - if (!checkPermission(l_info.acl_permission)) { + if (!checkPermission(packet->getPacketInfo().acl_permission)) { return; } - if (packet.getHeader() != "CH" && m_joined) { + if (packet->getPacketInfo().header != "CH" && m_joined) { if (m_is_afk) sendServerMessage("You are no longer AFK."); m_is_afk = false; m_afk_timer->start(ConfigManager::afkTimeout() * 1000); } - if (packet.getContent().length() < l_info.minArgs) { + if (packet->getContent().length() < packet->getPacketInfo().min_args) { #ifdef NET_DEBUG - qDebug() << "Invalid packet args length. Minimum is" << l_info.minArgs << "but only" << packet.getContent().length() << "were given."; + qDebug() << "Invalid packet args length. Minimum is" << packet->getPacketInfo().min_args << "but only" << packet->getContent().length() << "were given."; #endif return; } - (this->*(l_info.action))(l_area, packet.getContent().length(), packet.getContent(), packet); + packet->handlePacket(l_area, *this); } void AOClient::changeArea(int new_area) @@ -385,7 +384,7 @@ void AOClient::arup(ARUPType type, bool broadcast) } } if (broadcast) - server->broadcast(AOPacket("ARUP", l_arup_data)); + server->broadcast(PacketFactory::createPacket("ARUP", l_arup_data)); else sendPacket("ARUP", l_arup_data); } @@ -398,22 +397,22 @@ void AOClient::fullArup() arup(ARUPType::LOCKED, false); } -void AOClient::sendPacket(AOPacket packet) +void AOClient::sendPacket(AOPacket *packet) { #ifdef NET_DEBUG - qDebug() << "Sent packet:" << packet.getHeader() << ":" << packet.getContent(); + qDebug() << "Sent packet:" << packet->getPacketInfo().header << ":" << packet->getContent(); #endif m_socket->write(packet); } void AOClient::sendPacket(QString header, QStringList contents) { - sendPacket(AOPacket(header, contents)); + sendPacket(PacketFactory::createPacket(header, contents)); } void AOClient::sendPacket(QString header) { - sendPacket(AOPacket(header, {})); + sendPacket(PacketFactory::createPacket(header, {})); } void AOClient::calculateIpid() @@ -438,12 +437,12 @@ void AOClient::sendServerMessage(QString message) void AOClient::sendServerMessageArea(QString message) { - server->broadcast(AOPacket("CT", {ConfigManager::serverName(), message, "1"}), m_current_area); + server->broadcast(PacketFactory::createPacket("CT", {ConfigManager::serverName(), message, "1"}), m_current_area); } void AOClient::sendServerBroadcast(QString message) { - server->broadcast(AOPacket("CT", {ConfigManager::serverName(), message, "1"})); + server->broadcast(PacketFactory::createPacket("CT", {ConfigManager::serverName(), message, "1"})); } bool AOClient::checkPermission(ACLRole::Permission f_permission) const @@ -516,10 +515,10 @@ AOClient::AOClient(Server *p_server, NetworkSocket *socket, QObject *parent, int m_current_area(0), m_current_char(""), m_socket(socket), - server(p_server), - is_partial(false), + m_music_manager(p_manager), m_last_wtce_time(0), - m_music_manager(p_manager) + server(p_server), + is_partial(false) { m_afk_timer = new QTimer; m_afk_timer->setSingleShot(true); diff --git a/core/src/area_data.cpp b/core/src/area_data.cpp index 0bbf8a0..061bc68 100644 --- a/core/src/area_data.cpp +++ b/core/src/area_data.cpp @@ -21,7 +21,7 @@ #include "include/area_data.h" #include "include/config_manager.h" #include "include/music_manager.h" -#include "include/network/aopacket.h" +#include "include/packet/packet_factory.h" AreaData::AreaData(QString p_name, int p_index, MusicManager *p_music_manager = nullptr) : m_index(p_index), @@ -108,7 +108,7 @@ void AreaData::clientJoinedArea(int f_charId, int f_userId) emit userJoinedArea(m_index, f_userId); // The name will never be shown as we are using a spectator ID. Still nice for people who network sniff. // We auto-loop this so you'll never sit in silence unless wanted. - emit sendAreaPacketClient(AOPacket("MC", {m_currentMusic, QString::number(-1), ConfigManager::serverName(), QString::number(1)}), f_userId); + emit sendAreaPacketClient(PacketFactory::createPacket("MC", {m_currentMusic, QString::number(-1), ConfigManager::serverName(), QString::number(1)}), f_userId); } QList AreaData::owners() const @@ -624,7 +624,7 @@ QString AreaData::addJukeboxSong(QString f_song) if (l_song.second > 0) { if (m_jukebox_queue.size() == 0) { - emit sendAreaPacket(AOPacket("MC", {l_song.first, QString::number(-1)}), index()); + emit sendAreaPacket(PacketFactory::createPacket("MC", {l_song.first, QString::number(-1)}), index()); m_jukebox_timer->start(l_song.second * 1000); setCurrentMusic(f_song); setMusicPlayedBy("Jukebox"); @@ -650,7 +650,7 @@ void AreaData::switchJukeboxSong() if (m_jukebox_queue.size() == 1) { l_song_name = m_jukebox_queue[0]; QPair l_song = m_music_manager->songInformation(l_song_name, index()); - emit sendAreaPacket(AOPacket("MC", {l_song.first, "-1"}), m_index); + emit sendAreaPacket(PacketFactory::createPacket("MC", {l_song.first, "-1"}), m_index); m_jukebox_timer->start(l_song.second * 1000); } else { @@ -658,7 +658,7 @@ void AreaData::switchJukeboxSong() l_song_name = m_jukebox_queue[l_random_index]; QPair l_song = m_music_manager->songInformation(l_song_name, index()); - emit sendAreaPacket(AOPacket("MC", {l_song.first, "-1"}), m_index); + emit sendAreaPacket(PacketFactory::createPacket("MC", {l_song.first, "-1"}), m_index); m_jukebox_timer->start(l_song.second * 1000); m_jukebox_queue.remove(l_random_index); diff --git a/core/src/commands/area.cpp b/core/src/commands/area.cpp index 731cff8..c751561 100644 --- a/core/src/commands/area.cpp +++ b/core/src/commands/area.cpp @@ -19,7 +19,7 @@ #include "include/area_data.h" #include "include/config_manager.h" -#include "include/network/aopacket.h" +#include "include/packet/packet_factory.h" #include "include/server.h" // This file is for commands under the area category in aoclient.h @@ -298,7 +298,7 @@ void AOClient::cmdSetBackground(int argc, QStringList argv) if (m_authenticated || !area->bgLocked()) { if (server->getBackgrounds().contains(f_background, Qt::CaseInsensitive) || area->ignoreBgList() == true) { area->setBackground(f_background); - server->broadcast(AOPacket("BN", {f_background}), m_current_area); + server->broadcast(PacketFactory::createPacket("BN", {f_background}), m_current_area); sendServerMessageArea(m_current_char + " changed the background to " + f_background); } else { @@ -321,7 +321,7 @@ void AOClient::cmdBgLock(int argc, QStringList argv) l_area->toggleBgLock(); }; - server->broadcast(AOPacket("CT", {ConfigManager::serverName(), m_current_char + " locked the background.", "1"}), m_current_area); + server->broadcast(PacketFactory::createPacket("CT", {ConfigManager::serverName(), m_current_char + " locked the background.", "1"}), m_current_area); } void AOClient::cmdBgUnlock(int argc, QStringList argv) @@ -335,7 +335,7 @@ void AOClient::cmdBgUnlock(int argc, QStringList argv) l_area->toggleBgLock(); }; - server->broadcast(AOPacket("CT", {ConfigManager::serverName(), m_current_char + " unlocked the background.", "1"}), m_current_area); + server->broadcast(PacketFactory::createPacket("CT", {ConfigManager::serverName(), m_current_char + " unlocked the background.", "1"}), m_current_area); } void AOClient::cmdStatus(int argc, QStringList argv) @@ -347,7 +347,7 @@ void AOClient::cmdStatus(int argc, QStringList argv) if (l_area->changeStatus(l_arg)) { arup(ARUPType::STATUS, true); - server->broadcast(AOPacket("CT", {ConfigManager::serverName(), m_current_char + " changed status to " + l_arg.toUpper(), "1"}), m_current_area); + server->broadcast(PacketFactory::createPacket("CT", {ConfigManager::serverName(), m_current_char + " changed status to " + l_arg.toUpper(), "1"}), m_current_area); } else { const QStringList keys = AreaData::map_statuses.keys(); diff --git a/core/src/commands/casing.cpp b/core/src/commands/casing.cpp index ead5865..00f151d 100644 --- a/core/src/commands/casing.cpp +++ b/core/src/commands/casing.cpp @@ -19,7 +19,7 @@ #include "include/area_data.h" #include "include/config_manager.h" -#include "include/network/aopacket.h" +#include "include/packet/packet_factory.h" #include "include/server.h" // This file is for commands under the casing category in aoclient.h @@ -130,8 +130,8 @@ void AOClient::cmdExamine(int argc, QStringList argv) AreaData *l_area = server->getAreaById(m_current_area); if (l_area->testimony().size() - 1 > 0) { l_area->restartTestimony(); - server->broadcast(AOPacket("RT", {"testimony2"}), m_current_area); - server->broadcast(AOPacket("MS", {l_area->testimony()[0]}), m_current_area); + server->broadcast(PacketFactory::createPacket("RT", {"testimony2"}), m_current_area); + server->broadcast(PacketFactory::createPacket("MS", {l_area->testimony()[0]}), m_current_area); return; } if (l_area->testimonyRecording() == AreaData::TestimonyRecording::PLAYBACK) @@ -192,7 +192,7 @@ void AOClient::cmdPauseTestimony(int argc, QStringList argv) AreaData *l_area = server->getAreaById(m_current_area); l_area->setTestimonyRecording(AreaData::TestimonyRecording::STOPPED); - server->broadcast(AOPacket("RT", {"testimony1", "1"}), m_current_area); + server->broadcast(PacketFactory::createPacket("RT", {"testimony1", "1"}), m_current_area); sendServerMessage("Testimony has been stopped."); } diff --git a/core/src/commands/command_helper.cpp b/core/src/commands/command_helper.cpp index dd44181..b985591 100644 --- a/core/src/commands/command_helper.cpp +++ b/core/src/commands/command_helper.cpp @@ -19,7 +19,7 @@ #include "include/area_data.h" #include "include/config_manager.h" -#include "include/network/aopacket.h" +#include "include/packet/packet_factory.h" #include "include/server.h" // This file is for functions used by various commands, defined in the command helper function category in aoclient.h @@ -228,7 +228,7 @@ void AOClient::sendNotice(QString f_notice, bool f_global) l_message += "server-wide "; l_message += "notice:\n\n" + f_notice; sendServerMessageArea(l_message); - AOPacket l_packet("BB", {l_message}); + AOPacket *l_packet = PacketFactory::createPacket("BB", {l_message}); if (f_global) server->broadcast(l_packet); else diff --git a/core/src/commands/messaging.cpp b/core/src/commands/messaging.cpp index 9100fd0..bfb351a 100644 --- a/core/src/commands/messaging.cpp +++ b/core/src/commands/messaging.cpp @@ -18,7 +18,7 @@ #include "include/aoclient.h" #include "include/area_data.h" -#include "include/network/aopacket.h" +#include "include/packet/packet_factory.h" #include "include/server.h" // This file is for commands under the messaging category in aoclient.h @@ -77,8 +77,8 @@ void AOClient::cmdG(int argc, QStringList argv) QString l_sender_area = server->getAreaName(m_current_area); QString l_sender_message = argv.join(" "); // Better readability thanks to AwesomeAim. - AOPacket l_mod_packet = AOPacket("CT", {"[G][" + m_ipid + "][" + l_sender_area + "]" + l_sender_name, l_sender_message}); - AOPacket l_user_packet = AOPacket("CT", {"[G][" + l_sender_area + "]" + l_sender_name, l_sender_message}); + AOPacket *l_mod_packet = PacketFactory::createPacket("CT", {"[G][" + m_ipid + "][" + l_sender_area + "]" + l_sender_name, l_sender_message}); + AOPacket *l_user_packet = PacketFactory::createPacket("CT", {"[G][" + l_sender_area + "]" + l_sender_name, l_sender_message}); server->broadcast(l_user_packet, l_mod_packet, Server::TARGET_TYPE::AUTHENTICATED); return; } @@ -89,7 +89,7 @@ void AOClient::cmdNeed(int argc, QStringList argv) QString l_sender_area = server->getAreaName(m_current_area); QString l_sender_message = argv.join(" "); - server->broadcast(AOPacket("CT", {"=== Advert ===\n[" + l_sender_area + "] needs " + l_sender_message + "."}), Server::TARGET_TYPE::ADVERT); + server->broadcast(PacketFactory::createPacket("CT", {"=== Advert ===\n[" + l_sender_area + "] needs " + l_sender_message + "."}), Server::TARGET_TYPE::ADVERT); } void AOClient::cmdSwitch(int argc, QStringList argv) @@ -175,7 +175,7 @@ void AOClient::cmdM(int argc, QStringList argv) QString l_sender_name = m_ooc_name; QString l_sender_message = argv.join(" "); - server->broadcast(AOPacket("CT", {"[M]" + l_sender_name, l_sender_message}), Server::TARGET_TYPE::MODCHAT); + server->broadcast(PacketFactory::createPacket("CT", {"[M]" + l_sender_name, l_sender_message}), Server::TARGET_TYPE::MODCHAT); } void AOClient::cmdGM(int argc, QStringList argv) @@ -185,7 +185,7 @@ void AOClient::cmdGM(int argc, QStringList argv) QString l_sender_name = m_ooc_name; QString l_sender_area = server->getAreaName(m_current_area); QString l_sender_message = argv.join(" "); - server->broadcast(AOPacket("CT", {"[G][" + l_sender_area + "]" + "[" + l_sender_name + "][M]", l_sender_message}), Server::TARGET_TYPE::MODCHAT); + server->broadcast(PacketFactory::createPacket("CT", {"[G][" + l_sender_area + "]" + "[" + l_sender_name + "][M]", l_sender_message}), Server::TARGET_TYPE::MODCHAT); } void AOClient::cmdLM(int argc, QStringList argv) @@ -194,7 +194,7 @@ void AOClient::cmdLM(int argc, QStringList argv) QString l_sender_name = m_ooc_name; QString l_sender_message = argv.join(" "); - server->broadcast(AOPacket("CT", {"[" + l_sender_name + "][M]", l_sender_message}), m_current_area); + server->broadcast(PacketFactory::createPacket("CT", {"[" + l_sender_name + "][M]", l_sender_message}), m_current_area); } void AOClient::cmdGimp(int argc, QStringList argv) @@ -524,7 +524,7 @@ void AOClient::cmdA(int argc, QStringList argv) argv.removeAt(0); QString l_sender_name = m_ooc_name; QString l_ooc_message = argv.join(" "); - server->broadcast(AOPacket("CT", {"[CM]" + l_sender_name, l_ooc_message}), l_area_id); + server->broadcast(PacketFactory::createPacket("CT", {"[CM]" + l_sender_name, l_ooc_message}), l_area_id); } void AOClient::cmdS(int argc, QStringList argv) @@ -537,7 +537,7 @@ void AOClient::cmdS(int argc, QStringList argv) for (int i = 0; i <= l_all_areas; i++) { if (server->getAreaById(i)->owners().contains(m_id)) - server->broadcast(AOPacket("CT", {"[CM]" + l_sender_name, l_ooc_message}), i); + server->broadcast(PacketFactory::createPacket("CT", {"[CM]" + l_sender_name, l_ooc_message}), i); } } diff --git a/core/src/commands/music.cpp b/core/src/commands/music.cpp index 0752473..a3693b7 100644 --- a/core/src/commands/music.cpp +++ b/core/src/commands/music.cpp @@ -19,7 +19,7 @@ #include "include/area_data.h" #include "include/music_manager.h" -#include "include/network/aopacket.h" +#include "include/packet/packet_factory.h" #include "include/server.h" // This file is for commands under the music category in aoclient.h @@ -41,7 +41,7 @@ void AOClient::cmdPlay(int argc, QStringList argv) else { l_area->changeMusic(m_showname, l_song); } - AOPacket music_change("MC", {l_song, QString::number(server->getCharID(m_current_char)), m_showname, "1", "0"}); + AOPacket *music_change = PacketFactory::createPacket("MC", {l_song, QString::number(server->getCharID(m_current_char)), m_showname, "1", "0"}); server->broadcast(music_change, m_current_area); } diff --git a/core/src/commands/roleplay.cpp b/core/src/commands/roleplay.cpp index e3dd245..3175f1b 100644 --- a/core/src/commands/roleplay.cpp +++ b/core/src/commands/roleplay.cpp @@ -19,7 +19,7 @@ #include "include/area_data.h" #include "include/config_manager.h" -#include "include/network/aopacket.h" +#include "include/packet/packet_factory.h" #include "include/server.h" // This file is for commands under the roleplay category in aoclient.h @@ -195,8 +195,8 @@ void AOClient::cmdTimer(int argc, QStringList argv) else l_requested_timer = l_area->timers().at(l_timer_id - 1); - AOPacket l_show_timer("TI", {QString::number(l_timer_id), "2"}); - AOPacket l_hide_timer("TI", {QString::number(l_timer_id), "3"}); + AOPacket *l_show_timer = PacketFactory::createPacket("TI", {QString::number(l_timer_id), "2"}); + AOPacket *l_hide_timer = PacketFactory::createPacket("TI", {QString::number(l_timer_id), "3"}); bool l_is_global = l_timer_id == 0; // Set the timer's time remaining if the second @@ -206,7 +206,7 @@ void AOClient::cmdTimer(int argc, QStringList argv) l_requested_timer->setInterval(QTime(0, 0).msecsTo(l_requested_time)); l_requested_timer->start(); sendServerMessage("Set timer " + QString::number(l_timer_id) + " to " + argv[1] + "."); - AOPacket l_update_timer("TI", {QString::number(l_timer_id), "0", QString::number(QTime(0, 0).msecsTo(l_requested_time))}); + AOPacket *l_update_timer = PacketFactory::createPacket("TI", {QString::number(l_timer_id), "0", QString::number(QTime(0, 0).msecsTo(l_requested_time))}); l_is_global ? server->broadcast(l_show_timer) : server->broadcast(l_show_timer, m_current_area); // Show the timer l_is_global ? server->broadcast(l_update_timer) : server->broadcast(l_update_timer, m_current_area); return; @@ -216,7 +216,7 @@ void AOClient::cmdTimer(int argc, QStringList argv) if (argv[1] == "start") { l_requested_timer->start(); sendServerMessage("Started timer " + QString::number(l_timer_id) + "."); - AOPacket l_update_timer("TI", {QString::number(l_timer_id), "0", QString::number(QTime(0, 0).msecsTo(QTime(0, 0).addMSecs(l_requested_timer->remainingTime())))}); + AOPacket *l_update_timer = PacketFactory::createPacket("TI", {QString::number(l_timer_id), "0", QString::number(QTime(0, 0).msecsTo(QTime(0, 0).addMSecs(l_requested_timer->remainingTime())))}); l_is_global ? server->broadcast(l_show_timer) : server->broadcast(l_show_timer, m_current_area); l_is_global ? server->broadcast(l_update_timer) : server->broadcast(l_update_timer, m_current_area); } @@ -224,7 +224,7 @@ void AOClient::cmdTimer(int argc, QStringList argv) l_requested_timer->setInterval(l_requested_timer->remainingTime()); l_requested_timer->stop(); sendServerMessage("Stopped timer " + QString::number(l_timer_id) + "."); - AOPacket l_update_timer("TI", {QString::number(l_timer_id), "1", QString::number(QTime(0, 0).msecsTo(QTime(0, 0).addMSecs(l_requested_timer->interval())))}); + AOPacket *l_update_timer = PacketFactory::createPacket("TI", {QString::number(l_timer_id), "1", QString::number(QTime(0, 0).msecsTo(QTime(0, 0).addMSecs(l_requested_timer->interval())))}); l_is_global ? server->broadcast(l_update_timer) : server->broadcast(l_update_timer, m_current_area); } else if (argv[1] == "hide" || argv[1] == "unset") { diff --git a/core/src/music_manager.cpp b/core/src/music_manager.cpp index 28d63e1..310d35a 100644 --- a/core/src/music_manager.cpp +++ b/core/src/music_manager.cpp @@ -1,7 +1,7 @@ #include "include/music_manager.h" #include "include/config_manager.h" -#include "include/network/aopacket.h" +#include "include/packet/packet_factory.h" MusicManager::MusicManager(QStringList f_root_ordered, QStringList f_cdns, QMap> f_root_list, QObject *parent) : QObject(parent), @@ -120,7 +120,7 @@ bool MusicManager::addCustomSong(QString f_song_name, QString f_real_name, int f l_custom_list.insert(l_song_name, {l_real_name, f_duration}); m_custom_lists->insert(f_area_id, l_custom_list); m_customs_ordered.insert(f_area_id, (QStringList{m_customs_ordered.value(f_area_id)} << l_song_name)); - emit sendAreaFMPacket(AOPacket("FM", musiclist(f_area_id)), f_area_id); + emit sendAreaFMPacket(PacketFactory::createPacket("FM", musiclist(f_area_id)), f_area_id); return true; } @@ -148,7 +148,7 @@ bool MusicManager::addCustomCategory(QString f_category_name, int f_area_id) l_custom_list.insert(l_category_name, {l_category_name, 0}); m_custom_lists->insert(f_area_id, l_custom_list); m_customs_ordered.insert(f_area_id, (QStringList{m_customs_ordered.value(f_area_id)} << l_category_name)); - emit sendAreaFMPacket(AOPacket("FM", musiclist(f_area_id)), f_area_id); + emit sendAreaFMPacket(PacketFactory::createPacket("FM", musiclist(f_area_id)), f_area_id); return true; } @@ -165,7 +165,7 @@ bool MusicManager::removeCategorySong(QString f_songcategory_name, int f_area_id l_customs_ordered.removeAll(f_songcategory_name); m_customs_ordered.insert(f_area_id, l_customs_ordered); - emit sendAreaFMPacket(AOPacket("FM", musiclist(f_area_id)), f_area_id); + emit sendAreaFMPacket(PacketFactory::createPacket("FM", musiclist(f_area_id)), f_area_id); return true; } // Fallthrough } @@ -178,7 +178,7 @@ bool MusicManager::toggleRootEnabled(int f_area_id) if (m_global_enabled.value(f_area_id)) { sanitiseCustomList(f_area_id); } - emit sendAreaFMPacket(AOPacket("FM", musiclist(f_area_id)), f_area_id); + emit sendAreaFMPacket(PacketFactory::createPacket("FM", musiclist(f_area_id)), f_area_id); return m_global_enabled.value(f_area_id); } @@ -234,5 +234,5 @@ void MusicManager::reloadRequest() void MusicManager::userJoinedArea(int f_area_index, int f_user_id) { - emit sendFMPacket(AOPacket("FM", musiclist(f_area_index)), f_user_id); + emit sendFMPacket(PacketFactory::createPacket("FM", musiclist(f_area_index)), f_user_id); } diff --git a/core/src/network/aopacket.cpp b/core/src/network/aopacket.cpp index a82d7b1..43afe5f 100644 --- a/core/src/network/aopacket.cpp +++ b/core/src/network/aopacket.cpp @@ -17,50 +17,42 @@ ////////////////////////////////////////////////////////////////////////////////////// #include "include/network/aopacket.h" -AOPacket::AOPacket(QString p_header, QStringList p_contents) : - m_header(p_header), +#include "include/packet/packet_askchaa.h" +#include "include/packet/packet_casea.h" +#include "include/packet/packet_cc.h" +#include "include/packet/packet_ch.h" +#include "include/packet/packet_ct.h" +#include "include/packet/packet_de.h" +#include "include/packet/packet_ee.h" +#include "include/packet/packet_factory.h" +#include "include/packet/packet_hi.h" +#include "include/packet/packet_hp.h" +#include "include/packet/packet_id.h" +#include "include/packet/packet_mc.h" +#include "include/packet/packet_ms.h" +#include "include/packet/packet_pe.h" +#include "include/packet/packet_pw.h" +#include "include/packet/packet_rc.h" +#include "include/packet/packet_rd.h" +#include "include/packet/packet_rm.h" +#include "include/packet/packet_rt.h" +#include "include/packet/packet_setcase.h" +#include "include/packet/packet_zz.h" + +AOPacket::AOPacket(QStringList p_contents) : m_content(p_contents), m_escaped(false) { } -AOPacket::AOPacket(QString f_packet) -{ - QString l_packet = f_packet; - if (l_packet.isEmpty() || l_packet.at(0) == '#' || l_packet.contains("%")) { -#if NET_DEBUG - qDebug() << "Invalid or fantacrypt packet received."; -#endif - m_header = "Unknown"; - m_content = QStringList{"Unknown"}; - return; - } - - QStringList l_split_packet = l_packet.split("#"); - m_header = l_split_packet.value(0); - - // Remove header and trailing packetFinished - l_split_packet.removeFirst(); - l_split_packet.removeLast(); - m_content = l_split_packet; - - // All incoming data has to be escaped after being split. - this->unescapeContent(); -} - const QStringList AOPacket::getContent() { return m_content; } -QString AOPacket::getHeader() -{ - return m_header; -} - QString AOPacket::toString() { - if (!isPacketEscaped() && !(m_header == "LE")) { + if (!isPacketEscaped() && !(getPacketInfo().header == "LE")) { // We will never send unescaped data to a client, unless its evidence. this->escapeContent(); } @@ -68,7 +60,7 @@ QString AOPacket::toString() // Of course AO has SOME expection to the rule. this->escapeEvidence(); } - return QString("%1#%2#%3").arg(m_header, m_content.join("#"), packetFinished); + return QString("%1#%2#%3").arg(getPacketInfo().header, m_content.join("#"), packetFinished); } QByteArray AOPacket::toUtf8() @@ -117,3 +109,27 @@ bool AOPacket::isPacketEscaped() { return m_escaped; } + +void AOPacket::registerPackets() +{ + PacketFactory::registerClass("askchaa"); + PacketFactory::registerClass("CASEA"); + PacketFactory::registerClass("CC"); + PacketFactory::registerClass("CH"); + PacketFactory::registerClass("CT"); + PacketFactory::registerClass("DE"); + PacketFactory::registerClass("EE"); + PacketFactory::registerClass("HI"); + PacketFactory::registerClass("HP"); + PacketFactory::registerClass("ID"); + PacketFactory::registerClass("MC"); + PacketFactory::registerClass("MS"); + PacketFactory::registerClass("PE"); + PacketFactory::registerClass("PW"); + PacketFactory::registerClass("RC"); + PacketFactory::registerClass("RD"); + PacketFactory::registerClass("RM"); + PacketFactory::registerClass("RT"); + PacketFactory::registerClass("SETCASE"); + PacketFactory::registerClass("ZZ"); +} diff --git a/core/src/network/network_socket.cpp b/core/src/network/network_socket.cpp index fc4d875..ceeddc6 100644 --- a/core/src/network/network_socket.cpp +++ b/core/src/network/network_socket.cpp @@ -16,6 +16,7 @@ // along with this program. If not, see . // ////////////////////////////////////////////////////////////////////////////////////// #include "include/network/network_socket.h" +#include "include/packet/packet_factory.h" NetworkSocket::NetworkSocket(QTcpSocket *f_socket, QObject *parent) : QObject(parent) @@ -106,7 +107,12 @@ void NetworkSocket::readData() } for (const QString &l_single_packet : qAsConst(l_all_packets)) { - AOPacket l_packet(l_single_packet); + AOPacket *l_packet = PacketFactory::createPacket(l_single_packet); + if (!l_packet) { + qDebug() << "Unimplemented packet: " << l_single_packet; + continue; + } + emit handlePacket(l_packet); } } @@ -127,19 +133,24 @@ void NetworkSocket::ws_readData(QString f_data) } for (const QString &l_single_packet : qAsConst(l_all_packets)) { - AOPacket l_packet(l_single_packet); + AOPacket *l_packet = PacketFactory::createPacket(l_single_packet); + if (!l_packet) { + qDebug() << "Unimplemented packet: " << l_single_packet; + continue; + } + emit handlePacket(l_packet); } } -void NetworkSocket::write(AOPacket f_packet) +void NetworkSocket::write(AOPacket *f_packet) { if (m_socket_type == TCP) { - m_client_socket.tcp->write(f_packet.toUtf8()); + m_client_socket.tcp->write(f_packet->toUtf8()); m_client_socket.tcp->flush(); } else { - m_client_socket.ws->sendTextMessage(f_packet.toString()); + m_client_socket.ws->sendTextMessage(f_packet->toString()); m_client_socket.ws->flush(); } } diff --git a/core/src/packet/packet_askchaa.cpp b/core/src/packet/packet_askchaa.cpp new file mode 100644 index 0000000..4e71e16 --- /dev/null +++ b/core/src/packet/packet_askchaa.cpp @@ -0,0 +1,36 @@ +#include "include/packet/packet_askchaa.h" +#include "include/config_manager.h" +#include "include/server.h" + +#include + +PacketAskchaa::PacketAskchaa(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketAskchaa::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 0, + .header = "askchaa"}; + return info; +} + +void PacketAskchaa::handlePacket(AreaData *area, AOClient &client) const +{ + Q_UNUSED(area) + // Evidence isn't loaded during this part anymore + // As a result, we can always send "0" for evidence length + // Client only cares about what it gets from LE + client.sendPacket("SI", {QString::number(client.getServer()->getCharacterCount()), "0", QString::number(client.getServer()->getAreaCount() + client.getServer()->getMusicList().length())}); +} + +bool PacketAskchaa::validatePacket() const +{ + if (m_content.size() > 0) { // Too many arguments. + return false; + } + return true; +} diff --git a/core/src/packet/packet_casea.cpp b/core/src/packet/packet_casea.cpp new file mode 100644 index 0000000..2dd7452 --- /dev/null +++ b/core/src/packet/packet_casea.cpp @@ -0,0 +1,82 @@ +#include "include/packet/packet_casea.h" +#include "include/akashiutils.h" +#include "include/packet/packet_factory.h" +#include "include/server.h" + +#include + +PacketCasea::PacketCasea(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketCasea::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 6, + .header = "Casea"}; + return info; +} + +void PacketCasea::handlePacket(AreaData *area, AOClient &client) const +{ + Q_UNUSED(area) + + QString l_case_title = m_content[0]; + QStringList l_needed_roles; + QList l_needs_list; + for (int i = 1; i <= 5; i++) { + bool is_int = false; + bool need = m_content[i].toInt(&is_int); + if (!is_int) + return; + l_needs_list.append(need); + } + QStringList l_roles = {"defense attorney", "prosecutor", "judge", "jurors", "stenographer"}; + for (int i = 0; i < 5; i++) { + if (l_needs_list[i]) + l_needed_roles.append(l_roles[i]); + } + if (l_needed_roles.isEmpty()) + return; + + QString l_message = "=== Case Announcement ===\r\n" + (client.m_ooc_name == "" ? client.m_current_char : client.m_ooc_name) + " needs " + l_needed_roles.join(", ") + " for " + (l_case_title == "" ? "a case" : l_case_title) + "!"; + + QList l_clients_to_alert; + // here lies morton, RIP +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + QSet l_needs_set(l_needs_list.begin(), l_needs_list.end()); +#else + QSet l_needs_set = l_needs_list.toSet(); +#endif + const QVector l_clients = client.getServer()->getClients(); + for (AOClient *l_client : l_clients) { +#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) + QSet l_matches(l_client->m_casing_preferences.begin(), l_client->m_casing_preferences.end()); + l_matches.intersect(l_needs_set); +#else + QSet l_matches = l_client->m_casing_preferences.toSet().intersect(l_needs_set); +#endif + if (!l_matches.isEmpty() && !l_clients_to_alert.contains(l_client)) + l_clients_to_alert.append(l_client); + } + + for (AOClient *l_client : l_clients_to_alert) { + l_client->sendPacket(PacketFactory::createPacket("CASEA", {l_message, m_content[1], m_content[2], m_content[3], m_content[4], m_content[5], "1"})); + // you may be thinking, "hey wait a minute the network protocol documentation doesn't mention that last argument!" + // if you are in fact thinking that, you are correct! it is not in the documentation! + // however for some inscrutable reason Attorney Online 2 will outright reject a CASEA packet that does not have + // at least 7 arguments despite only using the first 6. Cera, i kneel. you have truly broken me. + } +} + +bool PacketCasea::validatePacket() const +{ + for (int i = 1; i < m_content.size(); i++) { + if (!AkashiUtils::checkArgType(m_content.at(i))) { + return false; + } + } + return true; +} diff --git a/core/src/packet/packet_cc.cpp b/core/src/packet/packet_cc.cpp new file mode 100644 index 0000000..823ab5e --- /dev/null +++ b/core/src/packet/packet_cc.cpp @@ -0,0 +1,48 @@ +#include "include/packet/packet_cc.h" +#include "include/akashiutils.h" +#include "include/config_manager.h" +#include "include/server.h" + +#include + +PacketCC::PacketCC(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketCC::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 3, + .header = "CC"}; + return info; +} + +void PacketCC::handlePacket(AreaData *area, AOClient &client) const +{ + Q_UNUSED(area) + + bool argument_ok; + int l_selected_char_id = m_content[1].toInt(&argument_ok); + if (!argument_ok) { + l_selected_char_id = client.SPECTATOR_ID; + } + + if (l_selected_char_id < -1 || l_selected_char_id > client.getServer()->getCharacters().size() - 1) { + client.sendPacket("KK", {"A protocol error has been encountered.Packet : CC\nCharacter ID out of range."}); + client.m_socket->close(); + } + + if (client.changeCharacter(l_selected_char_id)) + client.m_char_id = l_selected_char_id; + + if (client.m_char_id > client.SPECTATOR_ID) { + client.setSpectator(false); + } +} + +bool PacketCC::validatePacket() const +{ + return AkashiUtils::checkArgType(m_content.at(1)); +} diff --git a/core/src/packet/packet_ch.cpp b/core/src/packet/packet_ch.cpp new file mode 100644 index 0000000..f583f15 --- /dev/null +++ b/core/src/packet/packet_ch.cpp @@ -0,0 +1,33 @@ +#include "include/packet/packet_ch.h" +#include "include/akashiutils.h" +#include "include/server.h" + +#include + +PacketCH::PacketCH(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketCH::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 1, + .header = "CH"}; + return info; +} + +void PacketCH::handlePacket(AreaData *area, AOClient &client) const +{ + Q_UNUSED(area) + // Why does this packet exist + // At least Crystal made it useful + // It is now used for ping measurement + client.sendPacket("CHECK"); +} + +bool PacketCH::validatePacket() const +{ + return AkashiUtils::checkArgType(m_content.at(0)); +} diff --git a/core/src/packet/packet_ct.cpp b/core/src/packet/packet_ct.cpp new file mode 100644 index 0000000..924fee6 --- /dev/null +++ b/core/src/packet/packet_ct.cpp @@ -0,0 +1,69 @@ +#include "include/packet/packet_ct.h" +#include "include/akashidefs.h" +#include "include/config_manager.h" +#include "include/packet/packet_factory.h" +#include "include/server.h" + +#include + +PacketCT::PacketCT(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketCT::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 2, + .header = "CT"}; + return info; +} + +void PacketCT::handlePacket(AreaData *area, AOClient &client) const +{ + if (client.m_is_ooc_muted) { + client.sendServerMessage("You are OOC muted, and cannot speak."); + return; + } + + client.m_ooc_name = client.dezalgo(m_content[0]).replace(QRegExp("\\[|\\]|\\{|\\}|\\#|\\$|\\%|\\&"), ""); // no fucky wucky shit here + if (client.m_ooc_name.isEmpty() || client.m_ooc_name == ConfigManager::serverName()) // impersonation & empty name protection + return; + + if (client.m_ooc_name.length() > 30) { + client.sendServerMessage("Your name is too long! Please limit it to under 30 characters."); + return; + } + + if (client.m_is_logging_in) { + client.loginAttempt(m_content[1]); + return; + } + + QString l_message = client.dezalgo(m_content[1]); + if (l_message.length() == 0 || l_message.length() > ConfigManager::maxCharacters()) + return; + AOPacket *final_packet = PacketFactory::createPacket("CT", {client.m_ooc_name, l_message, "0"}); + if (l_message.at(0) == '/') { + QStringList l_cmd_argv = l_message.split(" ", akashi::SkipEmptyParts); + QString l_command = l_cmd_argv[0].trimmed().toLower(); + l_command = l_command.right(l_command.length() - 1); + l_cmd_argv.removeFirst(); + int l_cmd_argc = l_cmd_argv.length(); + + client.handleCommand(l_command, l_cmd_argc, l_cmd_argv); + emit client.logCMD((client.m_current_char + " " + client.m_showname), client.m_ipid, client.m_ooc_name, l_command, l_cmd_argv, client.getServer()->getAreaById(client.m_current_area)->name()); + return; + } + else { + client.getServer()->broadcast(final_packet, client.m_current_area); + } + emit client.logOOC((client.m_current_char + " " + client.m_showname), client.m_ooc_name, client.m_ipid, area->name(), l_message); +} + +bool PacketCT::validatePacket() const +{ + // Nothing to validate. + return true; +} diff --git a/core/src/packet/packet_de.cpp b/core/src/packet/packet_de.cpp new file mode 100644 index 0000000..e8064af --- /dev/null +++ b/core/src/packet/packet_de.cpp @@ -0,0 +1,36 @@ +#include "include/packet/packet_de.h" +#include "include/akashiutils.h" +#include "include/server.h" + +#include + +PacketDE::PacketDE(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketDE::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 1, + .header = "DE"}; + return info; +} + +void PacketDE::handlePacket(AreaData *area, AOClient &client) const +{ + if (!client.checkEvidenceAccess(area)) + return; + bool is_int = false; + int l_idx = m_content[0].toInt(&is_int); + if (is_int && l_idx < area->evidence().size() && l_idx >= 0) { + area->deleteEvidence(l_idx); + } + client.sendEvidenceList(area); +} + +bool PacketDE::validatePacket() const +{ + return AkashiUtils::checkArgType(m_content.at(0)); +} diff --git a/core/src/packet/packet_ee.cpp b/core/src/packet/packet_ee.cpp new file mode 100644 index 0000000..5d5f69f --- /dev/null +++ b/core/src/packet/packet_ee.cpp @@ -0,0 +1,37 @@ +#include "include/packet/packet_ee.h" +#include "include/akashiutils.h" +#include "include/server.h" + +#include + +PacketEE::PacketEE(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketEE::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 4, + .header = "EE"}; + return info; +} + +void PacketEE::handlePacket(AreaData *area, AOClient &client) const +{ + if (!client.checkEvidenceAccess(area)) + return; + bool is_int = false; + int l_idx = m_content[0].toInt(&is_int); + AreaData::Evidence l_evi = {m_content[1], m_content[2], m_content[3]}; + if (is_int && l_idx < area->evidence().size() && l_idx >= 0) { + area->replaceEvidence(l_idx, l_evi); + } + client.sendEvidenceList(area); +} + +bool PacketEE::validatePacket() const +{ + return AkashiUtils::checkArgType(m_content.at(0)); +} diff --git a/core/src/packet/packet_factory.cpp b/core/src/packet/packet_factory.cpp new file mode 100644 index 0000000..cf2f098 --- /dev/null +++ b/core/src/packet/packet_factory.cpp @@ -0,0 +1,34 @@ +#include "include/packet/packet_factory.h" +#include "include/packet/packet_generic.h" + +AOPacket *PacketFactory::createPacket(QString header, QStringList contents) +{ + if (!class_map.count(header)) { + return createInstance(header, contents); + } + + return class_map[header](contents); +} + +AOPacket *PacketFactory::createPacket(QString raw_packet) +{ + QString header; + QStringList contents; + + if (raw_packet.at(0) == '#' || raw_packet.contains("%") || raw_packet.isEmpty()) { + qDebug() << "FantaCrypt or otherwise invalid packet received"; + return PacketFactory::createPacket("Unknown", {"Unknown"}); + } + + QStringList packet_contents = raw_packet.split("#"); + header = packet_contents[0]; + + packet_contents.removeFirst(); // Remove header + packet_contents.removeLast(); // Remove anything trailing after delimiter + contents = packet_contents; + + AOPacket *packet = PacketFactory::createPacket(header, contents); + packet->unescapeContent(); + + return packet; +} diff --git a/core/src/packet/packet_generic.cpp b/core/src/packet/packet_generic.cpp new file mode 100644 index 0000000..8a6e77b --- /dev/null +++ b/core/src/packet/packet_generic.cpp @@ -0,0 +1,31 @@ +#include "include/packet/packet_generic.h" + +#include + +PacketGeneric::PacketGeneric(QString header, QStringList contents) : + AOPacket(contents), + header(header) +{ +} + +PacketInfo PacketGeneric::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 0, + .header = header}; + return info; +} + +void PacketGeneric::handlePacket(AreaData *area, AOClient &client) const +{ + Q_UNUSED(area) + Q_UNUSED(client) + qDebug() << "ERROR: Cannot handle generic packet: " << header; + qDebug() << "Packet is either unimplemented, or is meant to be sent to client"; +} + +bool PacketGeneric::validatePacket() const +{ + return true; +} diff --git a/core/src/packet/packet_hi.cpp b/core/src/packet/packet_hi.cpp new file mode 100644 index 0000000..565ddf8 --- /dev/null +++ b/core/src/packet/packet_hi.cpp @@ -0,0 +1,50 @@ +#include "include/packet/packet_hi.h" +#include "include/akashiutils.h" +#include "include/db_manager.h" +#include "include/server.h" + +#include + +PacketHI::PacketHI(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketHI::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 1, + .header = "HI"}; + return info; +} + +void PacketHI::handlePacket(AreaData *area, AOClient &client) const +{ + Q_UNUSED(area) + + QString incoming_hwid = m_content[0]; + if (incoming_hwid.isEmpty() || !client.m_hwid.isEmpty()) { + // No double sending or empty HWIDs! + client.sendPacket("BD", {"A protocol error has been encountered. Packet : HI"}); + client.m_socket->close(); + return; + } + + client.m_hwid = incoming_hwid; + emit client.getServer()->logConnectionAttempt(client.m_remote_ip.toString(), client.m_ipid, client.m_hwid); + auto ban = client.getServer()->getDatabaseManager()->isHDIDBanned(client.m_hwid); + if (ban.first) { + client.sendPacket("BD", {ban.second + "\nBan ID: " + QString::number(client.getServer()->getDatabaseManager()->getBanID(client.m_hwid))}); + client.m_socket->close(); + return; + } + + client.sendPacket("ID", {QString::number(client.m_id), "akashi", QCoreApplication::applicationVersion()}); +} + +bool PacketHI::validatePacket() const +{ + // We can always convert a string to a string. No point in checking. + return true; +} diff --git a/core/src/packet/packet_hp.cpp b/core/src/packet/packet_hp.cpp new file mode 100644 index 0000000..f27c8f9 --- /dev/null +++ b/core/src/packet/packet_hp.cpp @@ -0,0 +1,50 @@ +#include "include/packet/packet_hp.h" +#include "include/akashiutils.h" +#include "include/packet/packet_factory.h" +#include "include/server.h" + +#include + +PacketHP::PacketHP(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketHP::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 2, + .header = "HP"}; + return info; +} + +void PacketHP::handlePacket(AreaData *area, AOClient &client) const +{ + if (client.m_is_wtce_blocked) { + client.sendServerMessage("You are blocked from using the judge controls."); + return; + } + int l_newValue = m_content.at(1).toInt(); + + if (m_content[0] == "1") { + area->changeHP(AreaData::Side::DEFENCE, l_newValue); + } + else if (m_content[0] == "2") { + area->changeHP(AreaData::Side::PROSECUTOR, l_newValue); + } + + client.getServer()->broadcast(PacketFactory::createPacket("HP", {"1", QString::number(area->defHP())}), area->index()); + client.getServer()->broadcast(PacketFactory::createPacket("HP", {"2", QString::number(area->proHP())}), area->index()); + + client.updateJudgeLog(area, &client, "updated the penalties"); +} + +bool PacketHP::validatePacket() const +{ + if (!AkashiUtils::checkArgType(m_content.at(0))) + return false; + if (!AkashiUtils::checkArgType(m_content.at(1))) + return false; + return true; +} diff --git a/core/src/packet/packet_id.cpp b/core/src/packet/packet_id.cpp new file mode 100644 index 0000000..517f08d --- /dev/null +++ b/core/src/packet/packet_id.cpp @@ -0,0 +1,76 @@ +#include "include/packet/packet_id.h" +#include "include/config_manager.h" +#include "include/server.h" + +#include + +PacketID::PacketID(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketID::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 2, + .header = "ID"}; + return info; +} + +void PacketID::handlePacket(AreaData *area, AOClient &client) const +{ + Q_UNUSED(area) + + if (client.m_version.major == 2) { + // No double sending of the ID packet! + client.sendPacket("BD", {"A protocol error has been encountered. Packet : ID"}); + client.m_socket->close(); + return; + } + + // Full feature list as of AO 2.8.5 + // The only ones that are critical to ensuring the server works are + // "noencryption" and "fastloading" + QStringList l_feature_list = { + "noencryption", "yellowtext", "prezoom", + "flipping", "customobjections", "fastloading", + "deskmod", "evidence", "cccc_ic_support", + "arup", "casing_alerts", "modcall_reason", + "looping_sfx", "additive", "effects", + "y_offset", "expanded_desk_mods", "auth_packet"}; + + client.m_version.string = m_content[1]; + QRegularExpression rx("\\b(\\d+)\\.(\\d+)\\.(\\d+)\\b"); // matches X.X.X (e.g. 2.9.0, 2.4.10, etc.) + QRegularExpressionMatch l_match = rx.match(client.m_version.string); + if (l_match.hasMatch()) { + client.m_version.release = l_match.captured(1).toInt(); + client.m_version.major = l_match.captured(2).toInt(); + client.m_version.minor = l_match.captured(3).toInt(); + } + if (m_content[0] == "webAO") { + client.m_version.release = 2; + client.m_version.major = 10; + client.m_version.minor = 0; + } + + if (client.m_version.release != 2) { + // No valid ID packet resolution. + client.sendPacket("BD", {"A protocol error has been encountered. Packet : ID\nMajor version not recognised."}); + client.m_socket->close(); + return; + } + + client.sendPacket("PN", {QString::number(client.getServer()->getPlayerCount()), QString::number(ConfigManager::maxPlayers()), ConfigManager::serverDescription()}); + client.sendPacket("FL", l_feature_list); + + if (ConfigManager::assetUrl().isValid()) { + QByteArray l_asset_url = ConfigManager::assetUrl().toEncoded(QUrl::EncodeSpaces); + client.sendPacket("ASS", {l_asset_url}); + } +} + +bool PacketID::validatePacket() const +{ + return true; +} diff --git a/core/src/packet/packet_mc.cpp b/core/src/packet/packet_mc.cpp new file mode 100644 index 0000000..710b916 --- /dev/null +++ b/core/src/packet/packet_mc.cpp @@ -0,0 +1,98 @@ +#include "include/packet/packet_mc.h" +#include "include/music_manager.h" +#include "include/packet/packet_factory.h" +#include "include/server.h" + +#include + +PacketMC::PacketMC(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketMC::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 2, + .header = "MC"}; + return info; +} + +void PacketMC::handlePacket(AreaData *area, AOClient &client) const +{ + // 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 l_argument = m_content[0]; + + if (client.getServer()->getMusicList().contains(l_argument) || client.m_music_manager->isCustom(client.m_current_area, l_argument) || l_argument == "~stop.mp3") { // ~stop.mp3 is a dummy track used by 2.9+ + // We have a song here + + if (client.m_is_spectator) { + client.sendServerMessage("Spectator are blocked from changing the music."); + return; + } + + if (client.m_is_dj_blocked) { + client.sendServerMessage("You are blocked from changing the music."); + return; + } + if (!area->isMusicAllowed() && !client.checkPermission(ACLRole::CM)) { + client.sendServerMessage("Music is disabled in this area."); + return; + } + QString l_effects; + if (m_content.length() >= 4) + l_effects = m_content[3]; + else + l_effects = "0"; + QString l_final_song; + + // As categories can be used to stop music we need to check if it has a dot for the extension. If not, we assume its a category. + if (!l_argument.contains(".")) + l_final_song = "~stop.mp3"; + else + l_final_song = l_argument; + + // Jukebox intercepts the direct playing of messages. + if (area->isjukeboxEnabled()) { + QString l_jukebox_reply = area->addJukeboxSong(l_final_song); + client.sendServerMessage(l_jukebox_reply); + return; + } + + if (l_final_song != "~stop.mp3") { + // We might have an aliased song. We check for its real songname and send it to the clients. + QPair l_song = client.m_music_manager->songInformation(l_final_song, client.m_current_area); + l_final_song = l_song.first; + } + AOPacket *l_music_change = PacketFactory::createPacket("MC", {l_final_song, m_content[1], client.m_showname, "1", "0", l_effects}); + client.getServer()->broadcast(l_music_change, client.m_current_area); + + // Since we can't ensure a user has their showname set, we check if its empty to prevent + //"played by ." in /currentmusic. + if (client.m_showname.isEmpty()) { + area->changeMusic(client.m_current_char, l_final_song); + return; + } + area->changeMusic(client.m_showname, l_final_song); + return; + } + + for (int i = 0; i < client.getServer()->getAreaCount(); i++) { + QString l_area = client.getServer()->getAreaName(i); + if (l_area == l_argument) { + client.changeArea(i); + break; + } + } +} + +bool PacketMC::validatePacket() const +{ + return true; +} diff --git a/core/src/packet/packet_ms.cpp b/core/src/packet/packet_ms.cpp new file mode 100644 index 0000000..dbdbda2 --- /dev/null +++ b/core/src/packet/packet_ms.cpp @@ -0,0 +1,436 @@ +#include "include/packet/packet_ms.h" +#include "include/config_manager.h" +#include "include/packet/packet_factory.h" +#include "include/server.h" + +#include + +PacketMS::PacketMS(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketMS::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 15, + .header = "MS"}; + return info; +} + +void PacketMS::handlePacket(AreaData *area, AOClient &client) const +{ + if (client.m_is_muted) { + client.sendServerMessage("You cannot speak while muted."); + return; + } + + if (!area->isMessageAllowed() || !client.getServer()->isMessageAllowed()) { + return; + } + + AOPacket *validated_packet = validateIcPacket(client); + if (validated_packet->getPacketInfo().header == "INVALID") + return; + + if (client.m_pos != "") + validated_packet->setContentField(5, client.m_pos); + + client.getServer()->broadcast(validated_packet, client.m_current_area); + emit client.logIC((client.m_current_char + " " + client.m_showname), client.m_ooc_name, client.m_ipid, client.getServer()->getAreaById(client.m_current_area)->name(), client.m_last_message); + area->updateLastICMessage(validated_packet->getContent()); + + area->startMessageFloodguard(ConfigManager::messageFloodguard()); + client.getServer()->startMessageFloodguard(ConfigManager::globalMessageFloodguard()); +} + +AOPacket *PacketMS::validateIcPacket(AOClient &client) const +{ + // 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. + + // This packet can be sent with a minimum required args of 15. + // 2.6+ extensions raise this to 19, and 2.8 further raises this to 26. + + AOPacket *l_invalid = PacketFactory::createPacket("INVALID", {}); + QStringList l_args; + if (client.isSpectator() || client.m_current_char.isEmpty() || !client.m_joined) + // Spectators cannot use IC + return l_invalid; + AreaData *area = client.getServer()->getAreaById(client.m_current_area); + if (area->lockStatus() == AreaData::LockStatus::SPECTATABLE && !area->invited().contains(client.m_id) && !client.checkPermission(ACLRole::BYPASS_LOCKS)) + // Non-invited players cannot speak in spectatable areas + return l_invalid; + + QList l_incoming_args; + for (const QString &l_arg : m_content) { + l_incoming_args.append(QVariant(l_arg)); + } + + // desk modifier + QStringList allowed_desk_mods; + allowed_desk_mods << "chat" + << "0" + << "1" + << "2" + << "3" + << "4" + << "5"; + QString l_incoming_deskmod = l_incoming_args[0].toString(); + if (allowed_desk_mods.contains(l_incoming_deskmod)) { + // **WARNING : THIS IS A HACK!** + // A proper solution would be to deprecate chat as an argument on the clientside + // instead of overwriting correct netcode behaviour on the serverside. + if (l_incoming_deskmod == "chat") { + l_args.append("1"); + } + else { + l_args.append(l_incoming_args[0].toString()); + } + } + else + return l_invalid; + + // preanim + l_args.append(l_incoming_args[1].toString()); + + // char name + if (client.m_current_char.toLower() != l_incoming_args[2].toString().toLower()) { + // Selected char is different from supplied folder name + // This means the user is INI-swapped + if (!area->iniswapAllowed()) { + if (!client.getServer()->getCharacters().contains(l_incoming_args[2].toString(), Qt::CaseInsensitive)) + return l_invalid; + } + qDebug() << "INI swap detected from " << client.getIpid(); + } + client.m_current_iniswap = l_incoming_args[2].toString(); + l_args.append(l_incoming_args[2].toString()); + + // emote + client.m_emote = l_incoming_args[3].toString(); + if (client.m_first_person) + client.m_emote = ""; + l_args.append(client.m_emote); + + // message text + if (l_incoming_args[4].toString().size() > ConfigManager::maxCharacters()) + return l_invalid; + + QString l_incoming_msg = client.dezalgo(l_incoming_args[4].toString().trimmed()); + if (!area->lastICMessage().isEmpty() && l_incoming_msg == area->lastICMessage()[4] && l_incoming_msg != "") + return l_invalid; + + if (l_incoming_msg == "" && area->blankpostingAllowed() == false) { + client.sendServerMessage("Blankposting has been forbidden in this area."); + return l_invalid; + } + + if (client.m_is_gimped) { + QString l_gimp_message = ConfigManager::gimpList().at((client.genRand(1, ConfigManager::gimpList().size() - 1))); + l_incoming_msg = l_gimp_message; + } + + if (client.m_is_shaken) { + QStringList l_parts = l_incoming_msg.split(" "); + + std::random_device rng; + std::mt19937 urng(rng()); + std::shuffle(l_parts.begin(), l_parts.end(), urng); + + l_incoming_msg = l_parts.join(" "); + } + + if (client.m_is_disemvoweled) { + QString l_disemvoweled_message = l_incoming_msg.remove(QRegExp("[AEIOUaeiou]")); + l_incoming_msg = l_disemvoweled_message; + } + + client.m_last_message = l_incoming_msg; + l_args.append(l_incoming_msg); + + // side + // this is validated clientside so w/e + l_args.append(l_incoming_args[5].toString()); + if (client.m_pos != l_incoming_args[5].toString()) { + client.m_pos = l_incoming_args[5].toString(); + client.updateEvidenceList(client.getServer()->getAreaById(client.m_current_area)); + } + + // sfx name + l_args.append(l_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 = l_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 l_invalid; + l_args.append(QString::number(emote_mod)); + + // char id + if (l_incoming_args[8].toInt() != client.m_char_id) + return l_invalid; + l_args.append(l_incoming_args[8].toString()); + + // sfx delay + l_args.append(l_incoming_args[9].toString()); + + // objection modifier + if (area->isShoutAllowed()) { + if (l_incoming_args[10].toString().contains("4")) { + // custom shout includes text metadata + l_args.append(l_incoming_args[10].toString()); + } + else { + int l_obj_mod = l_incoming_args[10].toInt(); + if ((l_obj_mod < 0) || (l_obj_mod > 4)) { + return l_invalid; + } + l_args.append(QString::number(l_obj_mod)); + } + } + else { + if (l_incoming_args[10].toString() != "0") { + client.sendServerMessage("Shouts have been disabled in this area."); + } + l_args.append("0"); + } + + // evidence + int evi_idx = l_incoming_args[11].toInt(); + if (evi_idx > area->evidence().length()) + return l_invalid; + l_args.append(QString::number(evi_idx)); + + // flipping + int l_flip = l_incoming_args[12].toInt(); + if (l_flip != 0 && l_flip != 1) + return l_invalid; + client.m_flipping = QString::number(l_flip); + l_args.append(client.m_flipping); + + // realization + int realization = l_incoming_args[13].toInt(); + if (realization != 0 && realization != 1) + return l_invalid; + l_args.append(QString::number(realization)); + + // text color + int text_color = l_incoming_args[14].toInt(); + if (text_color < 0 || text_color > 11) + return l_invalid; + l_args.append(QString::number(text_color)); + + // 2.6 packet extensions + if (l_incoming_args.length() >= 19) { + // showname + QString l_incoming_showname = client.dezalgo(l_incoming_args[15].toString().trimmed()); + if (!(l_incoming_showname == client.m_current_char || l_incoming_showname.isEmpty()) && !area->shownameAllowed()) { + client.sendServerMessage("Shownames are not allowed in this area!"); + return l_invalid; + } + if (l_incoming_showname.length() > 30) { + client.sendServerMessage("Your showname is too long! Please limit it to under 30 characters"); + return l_invalid; + } + + // if the raw input is not empty but the trimmed input is, use a single space + if (l_incoming_showname.isEmpty() && !l_incoming_args[15].toString().isEmpty()) + l_incoming_showname = " "; + l_args.append(l_incoming_showname); + client.m_showname = l_incoming_showname; + + // other char id + // things get a bit hairy here + // don't ask me how this works, because i don't know either + QStringList l_pair_data = l_incoming_args[16].toString().split("^"); + client.m_pairing_with = l_pair_data[0].toInt(); + QString l_front_back = ""; + if (l_pair_data.length() > 1) + l_front_back = "^" + l_pair_data[1]; + int l_other_charid = client.m_pairing_with; + bool l_pairing = false; + QString l_other_name = "0"; + QString l_other_emote = "0"; + QString l_other_offset = "0"; + QString l_other_flip = "0"; + for (int l_client_id : area->joinedIDs()) { + AOClient *l_client = client.getServer()->getClientByID(l_client_id); + if (l_client->m_pairing_with == client.m_char_id && l_other_charid != client.m_char_id && l_client->m_char_id == client.m_pairing_with && l_client->m_pos == client.m_pos) { + l_other_name = l_client->m_current_iniswap; + l_other_emote = l_client->m_emote; + l_other_offset = l_client->m_offset; + l_other_flip = l_client->m_flipping; + l_pairing = true; + } + } + if (!l_pairing) { + l_other_charid = -1; + l_front_back = ""; + } + l_args.append(QString::number(l_other_charid) + l_front_back); + l_args.append(l_other_name); + l_args.append(l_other_emote); + + // self offset + client.m_offset = l_incoming_args[17].toString(); + // versions 2.6-2.8 cannot validate y-offset so we send them just the x-offset + if ((client.m_version.release == 2) && (client.m_version.major == 6 || client.m_version.major == 7 || client.m_version.major == 8)) { + QString l_x_offset = client.m_offset.split("&")[0]; + l_args.append(l_x_offset); + QString l_other_x_offset = l_other_offset.split("&")[0]; + l_args.append(l_other_x_offset); + } + else { + l_args.append(client.m_offset); + l_args.append(l_other_offset); + } + l_args.append(l_other_flip); + + // immediate text processing + int l_immediate = l_incoming_args[18].toInt(); + if (area->forceImmediate()) { + if (l_args[7] == "1" || l_args[7] == "2") { + l_args[7] = "0"; + l_immediate = 1; + } + else if (l_args[7] == "6") { + l_args[7] = "5"; + l_immediate = 1; + } + } + if (l_immediate != 1 && l_immediate != 0) + return l_invalid; + l_args.append(QString::number(l_immediate)); + } + + // 2.8 packet extensions + if (l_incoming_args.length() >= 26) { + // sfx looping + int l_sfx_loop = l_incoming_args[19].toInt(); + if (l_sfx_loop != 0 && l_sfx_loop != 1) + return l_invalid; + l_args.append(QString::number(l_sfx_loop)); + + // screenshake + int l_screenshake = l_incoming_args[20].toInt(); + if (l_screenshake != 0 && l_screenshake != 1) + return l_invalid; + l_args.append(QString::number(l_screenshake)); + + // frames shake + l_args.append(l_incoming_args[21].toString()); + + // frames realization + l_args.append(l_incoming_args[22].toString()); + + // frames sfx + l_args.append(l_incoming_args[23].toString()); + + // additive + int l_additive = l_incoming_args[24].toInt(); + if (l_additive != 0 && l_additive != 1) + return l_invalid; + else if (area->lastICMessage().isEmpty()) { + l_additive = 0; + } + else if (!(client.m_char_id == area->lastICMessage()[8].toInt())) { + l_additive = 0; + } + else if (l_additive == 1) { + l_args[4].insert(0, " "); + } + l_args.append(QString::number(l_additive)); + + // effect + l_args.append(l_incoming_args[25].toString()); + } + + // Testimony playback + if (area->testimonyRecording() == AreaData::TestimonyRecording::RECORDING || area->testimonyRecording() == AreaData::TestimonyRecording::ADD) { + if (l_args[5] != "wit") + return PacketFactory::createPacket("MS", l_args); + + if (area->statement() == -1) { + l_args[4] = "~~\\n-- " + l_args[4] + " --"; + l_args[14] = "3"; + client.getServer()->broadcast(PacketFactory::createPacket("RT", {"testimony1"}), client.m_current_area); + } + client.addStatement(l_args); + } + else if (area->testimonyRecording() == AreaData::TestimonyRecording::UPDATE) { + l_args = client.updateStatement(l_args); + } + else if (area->testimonyRecording() == AreaData::TestimonyRecording::PLAYBACK) { + AreaData::TestimonyProgress l_progress; + + if (l_args[4] == ">") { + client.m_pos = "wit"; + auto l_statement = area->jumpToStatement(area->statement() + 1); + l_args = l_statement.first; + l_progress = l_statement.second; + + if (l_progress == AreaData::TestimonyProgress::LOOPED) { + client.sendServerMessageArea("Last statement reached. Looping to first statement."); + } + } + if (l_args[4] == "<") { + client.m_pos = "wit"; + auto l_statement = area->jumpToStatement(area->statement() - 1); + l_args = l_statement.first; + l_progress = l_statement.second; + + if (l_progress == AreaData::TestimonyProgress::STAYED_AT_FIRST) { + client.sendServerMessage("First statement reached."); + } + } + + QString l_decoded_message = client.decodeMessage(l_args[4]); // Get rid of that pesky encoding first. + QRegularExpression jump("(?>)(?[0,1,2,3,4,5,6,7,8,9]+)"); + QRegularExpressionMatch match = jump.match(l_decoded_message); + if (match.hasMatch()) { + client.m_pos = "wit"; + auto l_statement = area->jumpToStatement(match.captured("int").toInt()); + l_args = l_statement.first; + l_progress = l_statement.second; + + switch (l_progress) { + case AreaData::TestimonyProgress::LOOPED: + { + client.sendServerMessageArea("Last statement reached. Looping to first statement."); + break; + } + case AreaData::TestimonyProgress::STAYED_AT_FIRST: + { + client.sendServerMessage("First statement reached."); + Q_FALLTHROUGH(); + } + case AreaData::TestimonyProgress::OK: + default: + // No need to handle. + break; + } + } + } + + return PacketFactory::createPacket("MS", l_args); +} + +bool PacketMS::validatePacket() const +{ + return true; +} diff --git a/core/src/packet/packet_pe.cpp b/core/src/packet/packet_pe.cpp new file mode 100644 index 0000000..272c052 --- /dev/null +++ b/core/src/packet/packet_pe.cpp @@ -0,0 +1,32 @@ +#include "include/packet/packet_pe.h" +#include "include/server.h" + +#include + +PacketPE::PacketPE(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketPE::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 3, + .header = "PE"}; + return info; +} + +void PacketPE::handlePacket(AreaData *area, AOClient &client) const +{ + if (!client.checkEvidenceAccess(area)) + return; + AreaData::Evidence l_evi = {m_content[0], m_content[1], m_content[2]}; + area->appendEvidence(l_evi); + client.sendEvidenceList(area); +} + +bool PacketPE::validatePacket() const +{ + return true; +} diff --git a/core/src/packet/packet_pw.cpp b/core/src/packet/packet_pw.cpp new file mode 100644 index 0000000..dcaf84a --- /dev/null +++ b/core/src/packet/packet_pw.cpp @@ -0,0 +1,30 @@ +#include "include/packet/packet_pw.h" +#include "include/server.h" + +#include + +PacketPW::PacketPW(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketPW::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 1, + .header = "PW"}; + return info; +} + +void PacketPW::handlePacket(AreaData *area, AOClient &client) const +{ + Q_UNUSED(area) + + client.m_password = m_content[0]; +} + +bool PacketPW::validatePacket() const +{ + return true; +} diff --git a/core/src/packet/packet_rc.cpp b/core/src/packet/packet_rc.cpp new file mode 100644 index 0000000..02d429c --- /dev/null +++ b/core/src/packet/packet_rc.cpp @@ -0,0 +1,30 @@ +#include "include/packet/packet_rc.h" +#include "include/server.h" + +#include + +PacketRC::PacketRC(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketRC::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 0, + .header = "RC"}; + return info; +} + +void PacketRC::handlePacket(AreaData *area, AOClient &client) const +{ + Q_UNUSED(area) + + client.sendPacket("SC", client.getServer()->getCharacters()); +} + +bool PacketRC::validatePacket() const +{ + return true; +} diff --git a/core/src/packet/packet_rd.cpp b/core/src/packet/packet_rd.cpp new file mode 100644 index 0000000..6756488 --- /dev/null +++ b/core/src/packet/packet_rd.cpp @@ -0,0 +1,72 @@ +#include "include/packet/packet_rd.h" +#include "include/config_manager.h" +#include "include/server.h" + +#include + +PacketRD::PacketRD(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketRD::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 0, + .header = "RD"}; + return info; +} + +void PacketRD::handlePacket(AreaData *area, AOClient &client) const +{ + if (client.m_hwid == "") { + // No early connecting! + client.m_socket->close(); + return; + } + + if (client.m_joined) { + return; + } + + client.m_joined = true; + client.getServer()->updateCharsTaken(area); + client.sendEvidenceList(area); + client.sendPacket("HP", {"1", QString::number(area->defHP())}); + client.sendPacket("HP", {"2", QString::number(area->proHP())}); + client.sendPacket("FA", client.getServer()->getAreaNames()); + // Here lies OPPASS, the genius of FanatSors who send the modpass to everyone in plain text. + client.sendPacket("DONE"); + client.sendPacket("BN", {area->background()}); + + client.sendServerMessage("=== MOTD ===\r\n" + ConfigManager::motd() + "\r\n============="); + + client.fullArup(); // Give client all the area data + if (client.getServer()->timer->isActive()) { + client.sendPacket("TI", {"0", "2"}); + client.sendPacket("TI", {"0", "0", QString::number(QTime(0, 0).msecsTo(QTime(0, 0).addMSecs(client.getServer()->timer->remainingTime())))}); + } + else { + client.sendPacket("TI", {"0", "3"}); + } + const QList l_timers = area->timers(); + for (QTimer *l_timer : l_timers) { + int l_timer_id = area->timers().indexOf(l_timer) + 1; + if (l_timer->isActive()) { + client.sendPacket("TI", {QString::number(l_timer_id), "2"}); + client.sendPacket("TI", {QString::number(l_timer_id), "0", QString::number(QTime(0, 0).msecsTo(QTime(0, 0).addMSecs(l_timer->remainingTime())))}); + } + else { + client.sendPacket("TI", {QString::number(l_timer_id), "3"}); + } + } + emit client.joined(); + area->clientJoinedArea(-1, client.m_id); + client.arup(client.ARUPType::PLAYER_COUNT, true); // Tell everyone there is a new player +} + +bool PacketRD::validatePacket() const +{ + return true; +} diff --git a/core/src/packet/packet_rm.cpp b/core/src/packet/packet_rm.cpp new file mode 100644 index 0000000..99f2ce2 --- /dev/null +++ b/core/src/packet/packet_rm.cpp @@ -0,0 +1,30 @@ +#include "include/packet/packet_rm.h" +#include "include/server.h" + +#include + +PacketRM::PacketRM(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketRM::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 0, + .header = "RM"}; + return info; +} + +void PacketRM::handlePacket(AreaData *area, AOClient &client) const +{ + Q_UNUSED(area) + + client.sendPacket("SM", client.getServer()->getAreaNames() + client.getServer()->getMusicList()); +} + +bool PacketRM::validatePacket() const +{ + return true; +} diff --git a/core/src/packet/packet_rt.cpp b/core/src/packet/packet_rt.cpp new file mode 100644 index 0000000..104e3d4 --- /dev/null +++ b/core/src/packet/packet_rt.cpp @@ -0,0 +1,43 @@ +#include "include/packet/packet_rt.h" +#include "include/packet/packet_factory.h" +#include "include/server.h" + +#include + +PacketRT::PacketRT(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketRT::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 1, + .header = "RT"}; + return info; +} + +void PacketRT::handlePacket(AreaData *area, AOClient &client) const +{ + if (client.m_is_wtce_blocked) { + client.sendServerMessage("You are blocked from using the judge controls."); + return; + } + + if (!area->isWtceAllowed()) { + client.sendServerMessage("WTCE animations have been disabled in this area."); + return; + } + + if (QDateTime::currentDateTime().toSecsSinceEpoch() - client.m_last_wtce_time <= 5) + return; + client.m_last_wtce_time = QDateTime::currentDateTime().toSecsSinceEpoch(); + client.getServer()->broadcast(PacketFactory::createPacket("RT", m_content), client.m_current_area); + client.updateJudgeLog(area, &client, "WT/CE"); +} + +bool PacketRT::validatePacket() const +{ + return true; +} diff --git a/core/src/packet/packet_setcase.cpp b/core/src/packet/packet_setcase.cpp new file mode 100644 index 0000000..1453b35 --- /dev/null +++ b/core/src/packet/packet_setcase.cpp @@ -0,0 +1,38 @@ +#include "include/packet/packet_setcase.h" +#include "include/server.h" + +#include + +PacketSetcase::PacketSetcase(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketSetcase::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 7, + .header = "Setcase"}; + return info; +} + +void PacketSetcase::handlePacket(AreaData *area, AOClient &client) const +{ + Q_UNUSED(area) + + QList l_prefs_list; + for (int i = 2; i <= 6; i++) { + bool is_int = false; + bool pref = m_content[i].toInt(&is_int); + if (!is_int) + return; + l_prefs_list.append(pref); + } + client.m_casing_preferences = l_prefs_list; +} + +bool PacketSetcase::validatePacket() const +{ + return true; +} diff --git a/core/src/packet/packet_zz.cpp b/core/src/packet/packet_zz.cpp new file mode 100644 index 0000000..c04b098 --- /dev/null +++ b/core/src/packet/packet_zz.cpp @@ -0,0 +1,57 @@ +#include "include/packet/packet_zz.h" +#include "include/config_manager.h" +#include "include/packet/packet_factory.h" +#include "include/server.h" + +#include + +PacketZZ::PacketZZ(QStringList &contents) : + AOPacket(contents) +{ +} + +PacketInfo PacketZZ::getPacketInfo() const +{ + PacketInfo info{ + .acl_permission = ACLRole::Permission::NONE, + .min_args = 0, + .header = "ZZ"}; + return info; +} + +void PacketZZ::handlePacket(AreaData *area, AOClient &client) const +{ + QString l_name = client.m_ooc_name; + if (client.m_ooc_name.isEmpty()) + l_name = client.m_current_char; + + QString l_areaName = area->name(); + + QString l_modcallNotice = "!!!MODCALL!!!\nArea: " + l_areaName + "\nCaller: " + l_name + "\n"; + + if (!m_content[0].isEmpty()) + l_modcallNotice.append("Reason: " + m_content[0]); + else + l_modcallNotice.append("No reason given."); + + const QVector l_clients = client.getServer()->getClients(); + for (AOClient *l_client : l_clients) { + if (l_client->m_authenticated) + l_client->sendPacket(PacketFactory::createPacket("ZZ", {l_modcallNotice})); + } + emit client.logModcall((client.m_current_char + " " + client.m_showname), client.m_ipid, client.m_ooc_name, client.getServer()->getAreaById(client.m_current_area)->name()); + + if (ConfigManager::discordModcallWebhookEnabled()) { + QString l_name = client.m_ooc_name; + if (client.m_ooc_name.isEmpty()) + l_name = client.m_current_char; + + QString l_areaName = area->name(); + emit client.getServer()->modcallWebhookRequest(l_name, l_areaName, m_content.value(0), client.getServer()->getAreaBuffer(l_areaName)); + } +} + +bool PacketZZ::validatePacket() const +{ + return true; +} diff --git a/core/src/packets.cpp b/core/src/packets.cpp index 3a44235..2bef526 100644 --- a/core/src/packets.cpp +++ b/core/src/packets.cpp @@ -24,577 +24,10 @@ #include "include/config_manager.h" #include "include/db_manager.h" #include "include/music_manager.h" -#include "include/network/aopacket.h" +#include "include/packet/packet_factory.h" #include "include/server.h" -void AOClient::pktDefault(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(area); - Q_UNUSED(argc); - Q_UNUSED(argv); -#ifdef NET_DEBUG - qDebug() << "Unimplemented packet:" << packet.getHeader() << packet.getContent(); -#else - Q_UNUSED(packet); -#endif -} - -void AOClient::pktHardwareId(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(area); - Q_UNUSED(argc); - Q_UNUSED(packet); - - QString l_incoming_hwid = argv[0]; - if (l_incoming_hwid.isEmpty() || !m_hwid.isEmpty()) { - // No double sending or empty HWIDs! - sendPacket(AOPacket("BD", {"A protocol error has been encountered. Packet : HI"})); - m_socket->close(); - return; - } - - m_hwid = l_incoming_hwid; - emit server->logConnectionAttempt(m_remote_ip.toString(), m_ipid, m_hwid); - auto l_ban = server->getDatabaseManager()->isHDIDBanned(m_hwid); - if (l_ban.first) { - sendPacket("BD", {l_ban.second + "\nBan ID: " + QString::number(server->getDatabaseManager()->getBanID(m_hwid))}); - m_socket->close(); - return; - } - sendPacket("ID", {QString::number(m_id), "akashi", QCoreApplication::applicationVersion()}); -} - -void AOClient::pktSoftwareId(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(area); - Q_UNUSED(argc); - Q_UNUSED(packet); - - if (m_version.major == 2) { - // No double sending of the ID packet! - sendPacket(AOPacket("BD", {"A protocol error has been encountered. Packet : ID"})); - m_socket->close(); - return; - } - - // Full feature list as of AO 2.8.5 - // The only ones that are critical to ensuring the server works are - // "noencryption" and "fastloading" - QStringList l_feature_list = { - "noencryption", "yellowtext", "prezoom", - "flipping", "customobjections", "fastloading", - "deskmod", "evidence", "cccc_ic_support", - "arup", "casing_alerts", "modcall_reason", - "looping_sfx", "additive", "effects", - "y_offset", "expanded_desk_mods", "auth_packet"}; - - m_version.string = argv[1]; - QRegularExpression rx("\\b(\\d+)\\.(\\d+)\\.(\\d+)\\b"); // matches X.X.X (e.g. 2.9.0, 2.4.10, etc.) - QRegularExpressionMatch l_match = rx.match(m_version.string); - if (l_match.hasMatch()) { - m_version.release = l_match.captured(1).toInt(); - m_version.major = l_match.captured(2).toInt(); - m_version.minor = l_match.captured(3).toInt(); - } - if (argv[0] == "webAO") { - m_version.release = 2; - m_version.major = 10; - m_version.minor = 0; - } - - if (m_version.release != 2) { - // No valid ID packet resolution. - sendPacket(AOPacket("BD", {"A protocol error has been encountered. Packet : ID\nMajor version not recognised."})); - m_socket->close(); - return; - } - - sendPacket("PN", {QString::number(server->getPlayerCount()), QString::number(ConfigManager::maxPlayers()), ConfigManager::serverDescription()}); - sendPacket("FL", l_feature_list); - - if (ConfigManager::assetUrl().isValid()) { - QByteArray l_asset_url = ConfigManager::assetUrl().toEncoded(QUrl::EncodeSpaces); - sendPacket("ASS", {l_asset_url}); - } -} - -void AOClient::pktBeginLoad(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(area); - Q_UNUSED(argc); - Q_UNUSED(argv); - Q_UNUSED(packet); - - // Evidence isn't loaded during this part anymore - // As a result, we can always send "0" for evidence length - // Client only cares about what it gets from LE - sendPacket("SI", {QString::number(server->getCharacterCount()), "0", QString::number(server->getAreaCount() + server->getMusicList().length())}); -} - -void AOClient::pktRequestChars(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(area); - Q_UNUSED(argc); - Q_UNUSED(argv); - Q_UNUSED(packet); - - sendPacket("SC", server->getCharacters()); -} - -void AOClient::pktRequestMusic(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(area); - Q_UNUSED(argc); - Q_UNUSED(argv); - Q_UNUSED(packet); - - sendPacket("SM", server->getAreaNames() + server->getMusicList()); -} - -void AOClient::pktLoadingDone(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(argc); - Q_UNUSED(argv); - Q_UNUSED(packet); - - if (m_hwid == "") { - // No early connecting! - m_socket->close(); - return; - } - - if (m_joined) { - return; - } - - m_joined = true; - server->updateCharsTaken(area); - sendEvidenceList(area); - sendPacket("HP", {"1", QString::number(area->defHP())}); - sendPacket("HP", {"2", QString::number(area->proHP())}); - sendPacket("FA", server->getAreaNames()); - // Here lies OPPASS, the genius of FanatSors who send the modpass to everyone in plain text. - sendPacket("DONE"); - sendPacket("BN", {area->background()}); - - sendServerMessage("=== MOTD ===\r\n" + ConfigManager::motd() + "\r\n============="); - - fullArup(); // Give client all the area data - if (server->timer->isActive()) { - sendPacket("TI", {"0", "2"}); - sendPacket("TI", {"0", "0", QString::number(QTime(0, 0).msecsTo(QTime(0, 0).addMSecs(server->timer->remainingTime())))}); - } - else { - sendPacket("TI", {"0", "3"}); - } - const QList l_timers = area->timers(); - for (QTimer *l_timer : l_timers) { - int l_timer_id = area->timers().indexOf(l_timer) + 1; - if (l_timer->isActive()) { - sendPacket("TI", {QString::number(l_timer_id), "2"}); - sendPacket("TI", {QString::number(l_timer_id), "0", QString::number(QTime(0, 0).msecsTo(QTime(0, 0).addMSecs(l_timer->remainingTime())))}); - } - else { - sendPacket("TI", {QString::number(l_timer_id), "3"}); - } - } - emit joined(); - area->clientJoinedArea(-1, m_id); - arup(ARUPType::PLAYER_COUNT, true); // Tell everyone there is a new player -} - -void AOClient::pktCharPassword(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(area); - Q_UNUSED(argc); - Q_UNUSED(packet); - - m_password = argv[0]; -} - -void AOClient::pktSelectChar(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(area); - Q_UNUSED(argc); - Q_UNUSED(packet); - - bool argument_ok; - int l_selected_char_id = argv[1].toInt(&argument_ok); - if (!argument_ok) { - l_selected_char_id = SPECTATOR_ID; - } - - if (l_selected_char_id < -1 || l_selected_char_id > server->getCharacters().size() - 1) { - sendPacket(AOPacket("KK", {"A protocol error has been encountered.Packet : CC\nCharacter ID out of range."})); - m_socket->close(); - } - - if (changeCharacter(l_selected_char_id)) - m_char_id = l_selected_char_id; - - if (m_char_id > SPECTATOR_ID) { - setSpectator(false); - } -} - -void AOClient::pktIcChat(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(argc); - Q_UNUSED(argv); - - if (m_is_muted) { - sendServerMessage("You cannot speak while muted."); - return; - } - - if (!area->isMessageAllowed() || !server->isMessageAllowed()) { - return; - } - - AOPacket validated_packet = validateIcPacket(packet); - if (validated_packet.getHeader() == "INVALID") - return; - - if (m_pos != "") - validated_packet.setContentField(5, m_pos); - - server->broadcast(validated_packet, m_current_area); - emit logIC((m_current_char + " " + m_showname), m_ooc_name, m_ipid, server->getAreaById(m_current_area)->name(), m_last_message); - area->updateLastICMessage(validated_packet.getContent()); - - area->startMessageFloodguard(ConfigManager::messageFloodguard()); - server->startMessageFloodguard(ConfigManager::globalMessageFloodguard()); -} - -void AOClient::pktOocChat(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(argc); - Q_UNUSED(packet); - - if (m_is_ooc_muted) { - sendServerMessage("You are OOC muted, and cannot speak."); - return; - } - - m_ooc_name = dezalgo(argv[0]).replace(QRegExp("\\[|\\]|\\{|\\}|\\#|\\$|\\%|\\&"), ""); // no fucky wucky shit here - if (m_ooc_name.isEmpty() || m_ooc_name == ConfigManager::serverName()) // impersonation & empty name protection - return; - - if (m_ooc_name.length() > 30) { - sendServerMessage("Your name is too long! Please limit it to under 30 characters."); - return; - } - - if (m_is_logging_in) { - loginAttempt(argv[1]); - return; - } - - QString l_message = dezalgo(argv[1]); - if (l_message.length() == 0 || l_message.length() > ConfigManager::maxCharacters()) - return; - AOPacket final_packet("CT", {m_ooc_name, l_message, "0"}); - if (l_message.at(0) == '/') { - QStringList l_cmd_argv = l_message.split(" ", akashi::SkipEmptyParts); - QString l_command = l_cmd_argv[0].trimmed().toLower(); - l_command = l_command.right(l_command.length() - 1); - l_cmd_argv.removeFirst(); - int l_cmd_argc = l_cmd_argv.length(); - - handleCommand(l_command, l_cmd_argc, l_cmd_argv); - emit logCMD((m_current_char + " " + m_showname), m_ipid, m_ooc_name, l_command, l_cmd_argv, server->getAreaById(m_current_area)->name()); - return; - } - else { - server->broadcast(final_packet, m_current_area); - } - emit logOOC((m_current_char + " " + m_showname), m_ooc_name, m_ipid, area->name(), l_message); -} - -void AOClient::pktPing(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(area); - Q_UNUSED(argc); - Q_UNUSED(argv); - Q_UNUSED(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) -{ - Q_UNUSED(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 l_argument = argv[0]; - - if (server->getMusicList().contains(l_argument) || m_music_manager->isCustom(m_current_area, l_argument) || l_argument == "~stop.mp3") { // ~stop.mp3 is a dummy track used by 2.9+ - // We have a song here - - if (m_is_spectator) { - sendServerMessage("Spectator are blocked from changing the music."); - return; - } - - if (m_is_dj_blocked) { - sendServerMessage("You are blocked from changing the music."); - return; - } - if (!area->isMusicAllowed() && !checkPermission(ACLRole::CM)) { - sendServerMessage("Music is disabled in this area."); - return; - } - QString l_effects; - if (argc >= 4) - l_effects = argv[3]; - else - l_effects = "0"; - QString l_final_song; - - // As categories can be used to stop music we need to check if it has a dot for the extension. If not, we assume its a category. - if (!l_argument.contains(".")) - l_final_song = "~stop.mp3"; - else - l_final_song = l_argument; - - // Jukebox intercepts the direct playing of messages. - if (area->isjukeboxEnabled()) { - QString l_jukebox_reply = area->addJukeboxSong(l_final_song); - sendServerMessage(l_jukebox_reply); - return; - } - - if (l_final_song != "~stop.mp3") { - // We might have an aliased song. We check for its real songname and send it to the clients. - QPair l_song = m_music_manager->songInformation(l_final_song, m_current_area); - l_final_song = l_song.first; - } - AOPacket l_music_change("MC", {l_final_song, argv[1], m_showname, "1", "0", l_effects}); - server->broadcast(l_music_change, m_current_area); - - // Since we can't ensure a user has their showname set, we check if its empty to prevent - //"played by ." in /currentmusic. - if (m_showname.isEmpty()) { - area->changeMusic(m_current_char, l_final_song); - return; - } - area->changeMusic(m_showname, l_final_song); - return; - } - - for (int i = 0; i < server->getAreaCount(); i++) { - QString l_area = server->getAreaName(i); - if (l_area == l_argument) { - changeArea(i); - break; - } - } -} - -void AOClient::pktWtCe(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(argc); - Q_UNUSED(argv); - - if (m_is_wtce_blocked) { - sendServerMessage("You are blocked from using the judge controls."); - return; - } - - if (!area->isWtceAllowed()) { - sendServerMessage("WTCE animations have been disabled in this area."); - return; - } - - if (QDateTime::currentDateTime().toSecsSinceEpoch() - m_last_wtce_time <= 5) - return; - m_last_wtce_time = QDateTime::currentDateTime().toSecsSinceEpoch(); - server->broadcast(packet, m_current_area); - updateJudgeLog(area, this, "WT/CE"); -} - -void AOClient::pktHpBar(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(argc); - Q_UNUSED(packet); - - if (m_is_wtce_blocked) { - sendServerMessage("You are blocked from using the judge controls."); - return; - } - int l_newValue = argv.at(1).toInt(); - - if (argv[0] == "1") { - area->changeHP(AreaData::Side::DEFENCE, l_newValue); - } - else if (argv[0] == "2") { - area->changeHP(AreaData::Side::PROSECUTOR, l_newValue); - } - - server->broadcast(AOPacket("HP", {"1", QString::number(area->defHP())}), area->index()); - server->broadcast(AOPacket("HP", {"2", QString::number(area->proHP())}), area->index()); - - updateJudgeLog(area, this, "updated the penalties"); -} - -void AOClient::pktModCall(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(argc); - Q_UNUSED(argv); - - QString l_name = m_ooc_name; - if (m_ooc_name.isEmpty()) - l_name = m_current_char; - - QString l_areaName = area->name(); - - QString l_modcallNotice = "!!!MODCALL!!!\nArea: " + l_areaName + "\nCaller: " + l_name + "\n"; - - if (!packet.getContent()[0].isEmpty()) - l_modcallNotice.append("Reason: " + packet.getContent()[0]); - else - l_modcallNotice.append("No reason given."); - - const QVector l_clients = server->getClients(); - for (AOClient *l_client : l_clients) { - if (l_client->m_authenticated) - l_client->sendPacket(AOPacket("ZZ", {l_modcallNotice})); - } - emit logModcall((m_current_char + " " + m_showname), m_ipid, m_ooc_name, server->getAreaById(m_current_area)->name()); - - if (ConfigManager::discordModcallWebhookEnabled()) { - QString l_name = m_ooc_name; - if (m_ooc_name.isEmpty()) - l_name = m_current_char; - - QString l_areaName = area->name(); - emit server->modcallWebhookRequest(l_name, l_areaName, packet.getContent().value(0), server->getAreaBuffer(l_areaName)); - } -} - -void AOClient::pktAddEvidence(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(argc); - Q_UNUSED(packet); - - if (!checkEvidenceAccess(area)) - return; - AreaData::Evidence l_evi = {argv[0], argv[1], argv[2]}; - area->appendEvidence(l_evi); - sendEvidenceList(area); -} - -void AOClient::pktRemoveEvidence(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(argc); - Q_UNUSED(packet); - - if (!checkEvidenceAccess(area)) - return; - bool is_int = false; - int l_idx = argv[0].toInt(&is_int); - if (is_int && l_idx < area->evidence().size() && l_idx >= 0) { - area->deleteEvidence(l_idx); - } - sendEvidenceList(area); -} - -void AOClient::pktEditEvidence(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(argc); - Q_UNUSED(packet); - - if (!checkEvidenceAccess(area)) - return; - bool is_int = false; - int l_idx = argv[0].toInt(&is_int); - AreaData::Evidence l_evi = {argv[1], argv[2], argv[3]}; - if (is_int && l_idx < area->evidence().size() && l_idx >= 0) { - area->replaceEvidence(l_idx, l_evi); - } - sendEvidenceList(area); -} - -void AOClient::pktSetCase(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(area); - Q_UNUSED(argc); - Q_UNUSED(packet); - - QList l_prefs_list; - for (int i = 2; i <= 6; i++) { - bool is_int = false; - bool pref = argv[i].toInt(&is_int); - if (!is_int) - return; - l_prefs_list.append(pref); - } - m_casing_preferences = l_prefs_list; -} - -void AOClient::pktAnnounceCase(AreaData *area, int argc, QStringList argv, AOPacket packet) -{ - Q_UNUSED(area); - Q_UNUSED(argc); - Q_UNUSED(packet); - - QString l_case_title = argv[0]; - QStringList l_needed_roles; - QList l_needs_list; - for (int i = 1; i <= 5; i++) { - bool is_int = false; - bool need = argv[i].toInt(&is_int); - if (!is_int) - return; - l_needs_list.append(need); - } - QStringList l_roles = {"defense attorney", "prosecutor", "judge", "jurors", "stenographer"}; - for (int i = 0; i < 5; i++) { - if (l_needs_list[i]) - l_needed_roles.append(l_roles[i]); - } - if (l_needed_roles.isEmpty()) - return; - - QString l_message = "=== Case Announcement ===\r\n" + (m_ooc_name == "" ? m_current_char : m_ooc_name) + " needs " + l_needed_roles.join(", ") + " for " + (l_case_title == "" ? "a case" : l_case_title) + "!"; - - QList l_clients_to_alert; - // here lies morton, RIP -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - QSet l_needs_set(l_needs_list.begin(), l_needs_list.end()); -#else - QSet l_needs_set = l_needs_list.toSet(); -#endif - const QVector l_clients = server->getClients(); - for (AOClient *l_client : l_clients) { -#if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) - QSet l_matches(l_client->m_casing_preferences.begin(), l_client->m_casing_preferences.end()); - l_matches.intersect(l_needs_set); -#else - QSet l_matches = l_client->m_casing_preferences.toSet().intersect(l_needs_set); -#endif - if (!l_matches.isEmpty() && !l_clients_to_alert.contains(l_client)) - l_clients_to_alert.append(l_client); - } - - for (AOClient *l_client : l_clients_to_alert) { - l_client->sendPacket(AOPacket("CASEA", {l_message, argv[1], argv[2], argv[3], argv[4], argv[5], "1"})); - // you may be thinking, "hey wait a minute the network protocol documentation doesn't mention that last argument!" - // if you are in fact thinking that, you are correct! it is not in the documentation! - // however for some inscrutable reason Attorney Online 2 will outright reject a CASEA packet that does not have - // at least 7 arguments despite only using the first 6. Cera, i kneel. you have truly broken me. - } -} - -void AOClient::sendEvidenceList(AreaData *area) +void AOClient::sendEvidenceList(AreaData *area) const { const QVector l_clients = server->getClients(); for (AOClient *l_client : l_clients) { @@ -624,388 +57,7 @@ void AOClient::updateEvidenceList(AreaData *area) l_evidence_list.append(l_evidence_format.arg(evidence.name, evidence.description, evidence.image)); } - sendPacket(AOPacket("LE", l_evidence_list)); -} - -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. - - // This packet can be sent with a minimum required args of 15. - // 2.6+ extensions raise this to 19, and 2.8 further raises this to 26. - - AOPacket l_invalid("INVALID", {}); - QStringList l_args; - if (isSpectator() || m_current_char.isEmpty() || !m_joined) - // Spectators cannot use IC - return l_invalid; - AreaData *area = server->getAreaById(m_current_area); - if (area->lockStatus() == AreaData::LockStatus::SPECTATABLE && !area->invited().contains(m_id) && !checkPermission(ACLRole::BYPASS_LOCKS)) - // Non-invited players cannot speak in spectatable areas - return l_invalid; - - QList l_incoming_args; - for (const QString &l_arg : packet.getContent()) { - l_incoming_args.append(QVariant(l_arg)); - } - - // desk modifier - QStringList allowed_desk_mods; - allowed_desk_mods << "chat" - << "0" - << "1" - << "2" - << "3" - << "4" - << "5"; - QString l_incoming_deskmod = l_incoming_args[0].toString(); - if (allowed_desk_mods.contains(l_incoming_deskmod)) { - // **WARNING : THIS IS A HACK!** - // A proper solution would be to deprecate chat as an argument on the clientside - // instead of overwriting correct netcode behaviour on the serverside. - if (l_incoming_deskmod == "chat") { - l_args.append("1"); - } - else { - l_args.append(l_incoming_args[0].toString()); - } - } - else - return l_invalid; - - // preanim - l_args.append(l_incoming_args[1].toString()); - - // char name - if (m_current_char.toLower() != l_incoming_args[2].toString().toLower()) { - // Selected char is different from supplied folder name - // This means the user is INI-swapped - if (!area->iniswapAllowed()) { - if (!server->getCharacters().contains(l_incoming_args[2].toString(), Qt::CaseInsensitive)) - return l_invalid; - } - qDebug() << "INI swap detected from " << getIpid(); - } - m_current_iniswap = l_incoming_args[2].toString(); - l_args.append(l_incoming_args[2].toString()); - - // emote - m_emote = l_incoming_args[3].toString(); - if (m_first_person) - m_emote = ""; - l_args.append(m_emote); - - // message text - if (l_incoming_args[4].toString().size() > ConfigManager::maxCharacters()) - return l_invalid; - - QString l_incoming_msg = dezalgo(l_incoming_args[4].toString().trimmed()); - if (!area->lastICMessage().isEmpty() && l_incoming_msg == area->lastICMessage()[4] && l_incoming_msg != "") - return l_invalid; - - if (l_incoming_msg == "" && area->blankpostingAllowed() == false) { - sendServerMessage("Blankposting has been forbidden in this area."); - return l_invalid; - } - - if (m_is_gimped) { - QString l_gimp_message = ConfigManager::gimpList().at((genRand(1, ConfigManager::gimpList().size() - 1))); - l_incoming_msg = l_gimp_message; - } - - if (m_is_shaken) { - QStringList l_parts = l_incoming_msg.split(" "); - std::random_shuffle(l_parts.begin(), l_parts.end()); - l_incoming_msg = l_parts.join(" "); - } - - if (m_is_disemvoweled) { - QString l_disemvoweled_message = l_incoming_msg.remove(QRegExp("[AEIOUaeiou]")); - l_incoming_msg = l_disemvoweled_message; - } - - m_last_message = l_incoming_msg; - l_args.append(l_incoming_msg); - - // side - // this is validated clientside so w/e - l_args.append(l_incoming_args[5].toString()); - if (m_pos != l_incoming_args[5].toString()) { - m_pos = l_incoming_args[5].toString(); - updateEvidenceList(server->getAreaById(m_current_area)); - } - - // sfx name - l_args.append(l_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 = l_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 l_invalid; - l_args.append(QString::number(emote_mod)); - - // char id - if (l_incoming_args[8].toInt() != m_char_id) - return l_invalid; - l_args.append(l_incoming_args[8].toString()); - - // sfx delay - l_args.append(l_incoming_args[9].toString()); - - // objection modifier - if (area->isShoutAllowed()) { - if (l_incoming_args[10].toString().contains("4")) { - // custom shout includes text metadata - l_args.append(l_incoming_args[10].toString()); - } - else { - int l_obj_mod = l_incoming_args[10].toInt(); - if ((l_obj_mod < 0) || (l_obj_mod > 4)) { - return l_invalid; - } - l_args.append(QString::number(l_obj_mod)); - } - } - else { - if (l_incoming_args[10].toString() != "0") { - sendServerMessage("Shouts have been disabled in this area."); - } - l_args.append("0"); - } - - // evidence - int evi_idx = l_incoming_args[11].toInt(); - if (evi_idx > area->evidence().length()) - return l_invalid; - l_args.append(QString::number(evi_idx)); - - // flipping - int l_flip = l_incoming_args[12].toInt(); - if (l_flip != 0 && l_flip != 1) - return l_invalid; - m_flipping = QString::number(l_flip); - l_args.append(m_flipping); - - // realization - int realization = l_incoming_args[13].toInt(); - if (realization != 0 && realization != 1) - return l_invalid; - l_args.append(QString::number(realization)); - - // text color - int text_color = l_incoming_args[14].toInt(); - if (text_color < 0 || text_color > 11) - return l_invalid; - l_args.append(QString::number(text_color)); - - // 2.6 packet extensions - if (l_incoming_args.length() >= 19) { - // showname - QString l_incoming_showname = dezalgo(l_incoming_args[15].toString().trimmed()); - if (!(l_incoming_showname == m_current_char || l_incoming_showname.isEmpty()) && !area->shownameAllowed()) { - sendServerMessage("Shownames are not allowed in this area!"); - return l_invalid; - } - if (l_incoming_showname.length() > 30) { - sendServerMessage("Your showname is too long! Please limit it to under 30 characters"); - return l_invalid; - } - - // if the raw input is not empty but the trimmed input is, use a single space - if (l_incoming_showname.isEmpty() && !l_incoming_args[15].toString().isEmpty()) - l_incoming_showname = " "; - l_args.append(l_incoming_showname); - m_showname = l_incoming_showname; - - // other char id - // things get a bit hairy here - // don't ask me how this works, because i don't know either - QStringList l_pair_data = l_incoming_args[16].toString().split("^"); - m_pairing_with = l_pair_data[0].toInt(); - QString l_front_back = ""; - if (l_pair_data.length() > 1) - l_front_back = "^" + l_pair_data[1]; - int l_other_charid = m_pairing_with; - bool l_pairing = false; - QString l_other_name = "0"; - QString l_other_emote = "0"; - QString l_other_offset = "0"; - QString l_other_flip = "0"; - for (int l_client_id : area->joinedIDs()) { - AOClient *l_client = server->getClientByID(l_client_id); - if (l_client->m_pairing_with == m_char_id && l_other_charid != m_char_id && l_client->m_char_id == m_pairing_with && l_client->m_pos == m_pos) { - l_other_name = l_client->m_current_iniswap; - l_other_emote = l_client->m_emote; - l_other_offset = l_client->m_offset; - l_other_flip = l_client->m_flipping; - l_pairing = true; - } - } - if (!l_pairing) { - l_other_charid = -1; - l_front_back = ""; - } - l_args.append(QString::number(l_other_charid) + l_front_back); - l_args.append(l_other_name); - l_args.append(l_other_emote); - - // self offset - m_offset = l_incoming_args[17].toString(); - // versions 2.6-2.8 cannot validate y-offset so we send them just the x-offset - if ((m_version.release == 2) && (m_version.major == 6 || m_version.major == 7 || m_version.major == 8)) { - QString l_x_offset = m_offset.split("&")[0]; - l_args.append(l_x_offset); - QString l_other_x_offset = l_other_offset.split("&")[0]; - l_args.append(l_other_x_offset); - } - else { - l_args.append(m_offset); - l_args.append(l_other_offset); - } - l_args.append(l_other_flip); - - // immediate text processing - int l_immediate = l_incoming_args[18].toInt(); - if (area->forceImmediate()) { - if (l_args[7] == "1" || l_args[7] == "2") { - l_args[7] = "0"; - l_immediate = 1; - } - else if (l_args[7] == "6") { - l_args[7] = "5"; - l_immediate = 1; - } - } - if (l_immediate != 1 && l_immediate != 0) - return l_invalid; - l_args.append(QString::number(l_immediate)); - } - - // 2.8 packet extensions - if (l_incoming_args.length() >= 26) { - // sfx looping - int l_sfx_loop = l_incoming_args[19].toInt(); - if (l_sfx_loop != 0 && l_sfx_loop != 1) - return l_invalid; - l_args.append(QString::number(l_sfx_loop)); - - // screenshake - int l_screenshake = l_incoming_args[20].toInt(); - if (l_screenshake != 0 && l_screenshake != 1) - return l_invalid; - l_args.append(QString::number(l_screenshake)); - - // frames shake - l_args.append(l_incoming_args[21].toString()); - - // frames realization - l_args.append(l_incoming_args[22].toString()); - - // frames sfx - l_args.append(l_incoming_args[23].toString()); - - // additive - int l_additive = l_incoming_args[24].toInt(); - if (l_additive != 0 && l_additive != 1) - return l_invalid; - else if (area->lastICMessage().isEmpty()) { - l_additive = 0; - } - else if (!(m_char_id == area->lastICMessage()[8].toInt())) { - l_additive = 0; - } - else if (l_additive == 1) { - l_args[4].insert(0, " "); - } - l_args.append(QString::number(l_additive)); - - // effect - l_args.append(l_incoming_args[25].toString()); - } - - // Testimony playback - if (area->testimonyRecording() == AreaData::TestimonyRecording::RECORDING || area->testimonyRecording() == AreaData::TestimonyRecording::ADD) { - if (l_args[5] != "wit") - return AOPacket("MS", l_args); - - if (area->statement() == -1) { - l_args[4] = "~~\\n-- " + l_args[4] + " --"; - l_args[14] = "3"; - server->broadcast(AOPacket("RT", {"testimony1"}), m_current_area); - } - addStatement(l_args); - } - else if (area->testimonyRecording() == AreaData::TestimonyRecording::UPDATE) { - l_args = updateStatement(l_args); - } - else if (area->testimonyRecording() == AreaData::TestimonyRecording::PLAYBACK) { - AreaData::TestimonyProgress l_progress; - - if (l_args[4] == ">") { - m_pos = "wit"; - auto l_statement = area->jumpToStatement(area->statement() + 1); - l_args = l_statement.first; - l_progress = l_statement.second; - - if (l_progress == AreaData::TestimonyProgress::LOOPED) { - sendServerMessageArea("Last statement reached. Looping to first statement."); - } - } - if (l_args[4] == "<") { - m_pos = "wit"; - auto l_statement = area->jumpToStatement(area->statement() - 1); - l_args = l_statement.first; - l_progress = l_statement.second; - - if (l_progress == AreaData::TestimonyProgress::STAYED_AT_FIRST) { - sendServerMessage("First statement reached."); - } - } - - QString l_decoded_message = decodeMessage(l_args[4]); // Get rid of that pesky encoding first. - QRegularExpression jump("(?>)(?[0,1,2,3,4,5,6,7,8,9]+)"); - QRegularExpressionMatch match = jump.match(l_decoded_message); - if (match.hasMatch()) { - m_pos = "wit"; - auto l_statement = area->jumpToStatement(match.captured("int").toInt()); - l_args = l_statement.first; - l_progress = l_statement.second; - - switch (l_progress) { - case AreaData::TestimonyProgress::LOOPED: - { - sendServerMessageArea("Last statement reached. Looping to first statement."); - break; - } - case AreaData::TestimonyProgress::STAYED_AT_FIRST: - { - sendServerMessage("First statement reached."); - Q_FALLTHROUGH(); - } - case AreaData::TestimonyProgress::OK: - default: - // No need to handle. - break; - } - } - } - - return AOPacket("MS", l_args); + sendPacket(PacketFactory::createPacket("LE", l_evidence_list)); } QString AOClient::dezalgo(QString p_text) diff --git a/core/src/server.cpp b/core/src/server.cpp index f0fc972..e8b56d4 100644 --- a/core/src/server.cpp +++ b/core/src/server.cpp @@ -27,8 +27,8 @@ #include "include/discord.h" #include "include/logger/u_logger.h" #include "include/music_manager.h" -#include "include/network/aopacket.h" #include "include/network/network_socket.h" +#include "include/packet/packet_factory.h" Server::Server(int p_port, int p_ws_port, QObject *parent) : QObject(parent), @@ -56,6 +56,8 @@ Server::Server(int p_port, int p_ws_port, QObject *parent) : logger = new ULogger(this); connect(this, &Server::logConnectionAttempt, logger, &ULogger::logConnectionAttempt); + + AOPacket::registerPackets(); } void Server::start() @@ -115,7 +117,7 @@ void Server::start() ConfigManager::musiclist(); music_manager = new MusicManager(ConfigManager::ordered_songs(), ConfigManager::cdnList(), ConfigManager::musiclist(), this); connect(music_manager, &MusicManager::sendFMPacket, this, &Server::unicast); - connect(music_manager, &MusicManager::sendAreaFMPacket, this, QOverload::of(&Server::broadcast)); + connect(music_manager, &MusicManager::sendAreaFMPacket, this, QOverload::of(&Server::broadcast)); // Get musiclist from config file m_music_list = music_manager->rootMusiclist(); @@ -127,7 +129,7 @@ void Server::start() QString area_name = raw_area_names[i]; AreaData *l_area = new AreaData(area_name, i, music_manager); m_areas.insert(i, l_area); - connect(l_area, &AreaData::sendAreaPacket, this, QOverload::of(&Server::broadcast)); + connect(l_area, &AreaData::sendAreaPacket, this, QOverload::of(&Server::broadcast)); connect(l_area, &AreaData::sendAreaPacketClient, this, &Server::unicast); connect(l_area, &AreaData::userJoinedArea, music_manager, &MusicManager::userJoinedArea); music_manager->registerArea(i); @@ -162,8 +164,8 @@ void Server::clientConnected() // Too many players. Reject connection! // This also enforces the maximum playercount. if (m_available_ids.empty()) { - AOPacket disconnect_reason("BD", {"Maximum playercount has been reached."}); - socket->write(disconnect_reason.toUtf8()); + AOPacket *disconnect_reason = PacketFactory::createPacket("BD", {"Maximum playercount has been reached."}); + socket->write(disconnect_reason->toUtf8()); socket->flush(); socket->close(); socket->deleteLater(); @@ -192,8 +194,8 @@ void Server::clientConnected() if (is_banned) { QString reason = ban.second; - AOPacket ban_reason("BD", {reason}); - socket->write(ban_reason.toUtf8()); + AOPacket *ban_reason = PacketFactory::createPacket("BD", {reason}); + socket->write(ban_reason->toUtf8()); } if (is_banned || is_at_multiclient_limit) { socket->flush(); @@ -210,8 +212,8 @@ void Server::clientConnected() if (isIPBanned(l_remote_ip)) { QString l_reason = "Your IP has been banned by a moderator."; - AOPacket l_ban_reason("BD", {l_reason}); - socket->write(l_ban_reason.toUtf8()); + AOPacket *l_ban_reason = PacketFactory::createPacket("BD", {l_reason}); + socket->write(l_ban_reason->toUtf8()); client->deleteLater(); socket->close(); markIDFree(user_id); @@ -228,9 +230,10 @@ void Server::clientConnected() }); connect(l_socket, &NetworkSocket::handlePacket, client, &AOClient::handlePacket); - AOPacket decryptor("decryptor", {"NOENCRYPT"}); // This is the infamous workaround for - // tsuserver4. It should disable fantacrypt - // completely in any client 2.4.3 or newer + // This is the infamous workaround for + // tsuserver4. It should disable fantacrypt + // completely in any client 2.4.3 or newer + AOPacket *decryptor = PacketFactory::createPacket("decryptor", {"NOENCRYPT"}); client->sendPacket(decryptor); hookupAOClient(client); #ifdef NET_DEBUG @@ -246,7 +249,7 @@ void Server::ws_clientConnected() // Too many players. Reject connection! // This also enforces the maximum playercount. if (m_available_ids.empty()) { - AOPacket disconnect_reason("BD", {"Maximum playercount has been reached."}); + AOPacket *disconnect_reason = PacketFactory::createPacket("BD", {"Maximum playercount has been reached."}); l_socket->write(disconnect_reason); l_socket->close(); l_socket->deleteLater(); @@ -272,8 +275,8 @@ void Server::ws_clientConnected() if (is_banned) { QString reason = ban.second; - AOPacket ban_reason("BD", {reason}); - socket->sendTextMessage(ban_reason.toUtf8()); + AOPacket *ban_reason = PacketFactory::createPacket("BD", {reason}); + socket->sendTextMessage(ban_reason->toUtf8()); } if (is_banned || is_at_multiclient_limit) { client->deleteLater(); @@ -289,7 +292,7 @@ void Server::ws_clientConnected() if (isIPBanned(l_remote_ip)) { QString l_reason = "Your IP has been banned by a moderator."; - AOPacket l_ban_reason("BD", {l_reason}); + AOPacket *l_ban_reason = PacketFactory::createPacket("BD", {l_reason}); l_socket->write(l_ban_reason); client->deleteLater(); l_socket->close(QWebSocketProtocol::CloseCodeNormal); @@ -307,9 +310,10 @@ void Server::ws_clientConnected() }); connect(l_socket, &NetworkSocket::handlePacket, client, &AOClient::handlePacket); - AOPacket decryptor("decryptor", {"NOENCRYPT"}); // This is the infamous workaround for - // tsuserver4. It should disable fantacrypt - // completely in any client 2.4.3 or newer + // This is the infamous workaround for + // tsuserver4. It should disable fantacrypt + // completely in any client 2.4.3 or newer + AOPacket *decryptor = PacketFactory::createPacket("decryptor", {"NOENCRYPT"}); client->sendPacket(decryptor); hookupAOClient(client); } @@ -323,7 +327,7 @@ void Server::updateCharsTaken(AreaData *area) : QStringLiteral("0")); } - AOPacket response_cc("CharsCheck", chars_taken); + AOPacket *response_cc = PacketFactory::createPacket("CharsCheck", chars_taken); for (AOClient *l_client : qAsConst(m_clients)) { if (l_client->m_current_area == area->index()) { @@ -331,7 +335,7 @@ void Server::updateCharsTaken(AreaData *area) l_client->sendPacket(response_cc); else { QStringList chars_taken_cursed = getCursedCharsTaken(l_client, chars_taken); - AOPacket response_cc_cursed("CharsCheck", chars_taken_cursed); + AOPacket *response_cc_cursed = PacketFactory::createPacket("CharsCheck", chars_taken_cursed); l_client->sendPacket(response_cc_cursed); } } @@ -384,7 +388,7 @@ void Server::reloadSettings() command_extension_collection->loadFile("config/command_extensions.ini"); } -void Server::broadcast(AOPacket packet, int area_index) +void Server::broadcast(AOPacket *packet, int area_index) { QVector l_client_ids = m_areas.value(area_index)->joinedIDs(); for (const int l_client_id : qAsConst(l_client_ids)) { @@ -392,14 +396,14 @@ void Server::broadcast(AOPacket packet, int area_index) } } -void Server::broadcast(AOPacket packet) +void Server::broadcast(AOPacket *packet) { for (AOClient *l_client : qAsConst(m_clients)) { l_client->sendPacket(packet); } } -void Server::broadcast(AOPacket packet, TARGET_TYPE target) +void Server::broadcast(AOPacket *packet, TARGET_TYPE target) { for (AOClient *l_client : qAsConst(m_clients)) { switch (target) { @@ -419,7 +423,7 @@ void Server::broadcast(AOPacket packet, TARGET_TYPE target) } } -void Server::broadcast(AOPacket packet, AOPacket other_packet, TARGET_TYPE target) +void Server::broadcast(AOPacket *packet, AOPacket *other_packet, TARGET_TYPE target) { switch (target) { case TARGET_TYPE::AUTHENTICATED: @@ -437,7 +441,7 @@ void Server::broadcast(AOPacket packet, AOPacket other_packet, TARGET_TYPE targe } } -void Server::unicast(AOPacket f_packet, int f_client_id) +void Server::unicast(AOPacket *f_packet, int f_client_id) { AOClient *l_client = getClientByID(f_client_id); if (l_client != nullptr) { // This should never happen, but safety first. diff --git a/tests/tests.pro b/tests/tests.pro index 0d78c02..159abf1 100644 --- a/tests/tests.pro +++ b/tests/tests.pro @@ -5,6 +5,7 @@ SUBDIRS += \ unittest_music_manager \ unittest_acl_roles_handler \ unittest_command_extension \ - unittest_aopacket \ unittest_config_manager \ - unittest_crypto + unittest_crypto \ + unittest_aopacket \ + unittest_akashi_utils diff --git a/tests/tests_common.pri b/tests/tests_common.pri index 2f19985..f50decc 100644 --- a/tests/tests_common.pri +++ b/tests/tests_common.pri @@ -2,6 +2,8 @@ QT += network websockets core sql testlib CONFIG += qt console warn_on depend_includepath testcase no_testcase_installs CONFIG -= app_bundle +unix: CONFIG += c++1z +win32: CONFIG += c++2a coverage { LIBS += -lgcov diff --git a/tests/unittest_akashi_utils/tst_unittest_akashi_utils.cpp b/tests/unittest_akashi_utils/tst_unittest_akashi_utils.cpp new file mode 100644 index 0000000..2225e0c --- /dev/null +++ b/tests/unittest_akashi_utils/tst_unittest_akashi_utils.cpp @@ -0,0 +1,111 @@ +#include "include/akashiutils.h" +#include + +namespace tests { +namespace unittests { + +class tst_AkashiUtils : public QObject +{ + Q_OBJECT + + private slots: + + void integer_data(); + void integer(); + + void floating_data(); + void floating(); + + void boolean_data(); + void boolean(); + + void doublep_data(); + void doublep(); +}; + +void tst_AkashiUtils::integer_data() +{ + QTest::addColumn("content"); + QTest::addColumn("expected_result"); + + QTest::addRow("Integer (good)") << "one" + << false; + QTest::addRow("Integer (bad)") << "1" + << true; +} + +void tst_AkashiUtils::integer() +{ + QFETCH(QString, content); + QFETCH(bool, expected_result); + + bool result = AkashiUtils::checkArgType(content); + QCOMPARE(result, expected_result); +} + +void tst_AkashiUtils::floating_data() +{ + QTest::addColumn("content"); + QTest::addColumn("expected_result"); + + QTest::addRow("Float (good)") << "test" + << false; + QTest::addRow("Float (bad)") << "3.141" + << true; +} + +void tst_AkashiUtils::floating() +{ + QFETCH(QString, content); + QFETCH(bool, expected_result); + + bool result = AkashiUtils::checkArgType(content); + QCOMPARE(result, expected_result); +} + +void tst_AkashiUtils::boolean_data() +{ + QTest::addColumn("content"); + QTest::addColumn("expected_result"); + + QTest::addRow("Boolean (random string)") << "test" + << true; + QTest::addRow("Boolean (true/false string)") << "true" + << true; +} + +void tst_AkashiUtils::boolean() +{ + QFETCH(QString, content); + QFETCH(bool, expected_result); + + bool result = AkashiUtils::checkArgType(content); + QCOMPARE(result, expected_result); +} + +void tst_AkashiUtils::doublep_data() +{ + QTest::addColumn("content"); + QTest::addColumn("expected_result"); + + QTest::addRow("Double (good)") << "test" + << false; + QTest::addRow("Double (bad)") << "3.141592653589793" + << true; +} + +void tst_AkashiUtils::doublep() +{ + QFETCH(QString, content); + QFETCH(bool, expected_result); + + bool result = AkashiUtils::checkArgType(content); + QCOMPARE(result, expected_result); +} + +} +}; + +QTEST_APPLESS_MAIN(tests::unittests::tst_AkashiUtils) + +#include "tst_unittest_akashi_utils.moc" diff --git a/tests/unittest_akashi_utils/unittest_akashi_utils.pro b/tests/unittest_akashi_utils/unittest_akashi_utils.pro new file mode 100644 index 0000000..66c7980 --- /dev/null +++ b/tests/unittest_akashi_utils/unittest_akashi_utils.pro @@ -0,0 +1,5 @@ +QT -= gui + +include(../tests_common.pri) + +SOURCES += tst_unittest_akashi_utils.cpp diff --git a/tests/unittest_aopacket/tst_unittest_aopacket.cpp b/tests/unittest_aopacket/tst_unittest_aopacket.cpp index 0fe8a6a..3232610 100644 --- a/tests/unittest_aopacket/tst_unittest_aopacket.cpp +++ b/tests/unittest_aopacket/tst_unittest_aopacket.cpp @@ -2,6 +2,7 @@ #include #include "include/network/aopacket.h" +#include "include/packet/packet_factory.h" namespace tests { namespace unittests { @@ -14,9 +15,12 @@ class Packet : public QObject Q_OBJECT public: - AOPacket m_packet = AOPacket{"", {}}; - private slots: + /** + * @brief Initializes all tests + */ + void init(); + /** * @brief Creates a packet from a defined header and content. */ @@ -33,11 +37,16 @@ class Packet : public QObject void createPacketFromString(); }; +void Packet::init() +{ + AOPacket::registerPackets(); +} + void Packet::createPacket() { - AOPacket packet = AOPacket("HI", {"HDID"}); - QCOMPARE(packet.getHeader(), "HI"); - QCOMPARE(packet.getContent(), {"HDID"}); + AOPacket *packet = PacketFactory::createPacket("HI", {"HDID"}); + QCOMPARE(packet->getPacketInfo().header, "HI"); + QCOMPARE(packet->getContent(), {"HDID"}); } void Packet::createPacketFromString_data() @@ -64,7 +73,11 @@ void Packet::createPacketFromString_data() QTest::newRow("Unescaped characters") << "MC#20% Cooler#" << "Unknown" - << QStringList{"Unknown"}; // This should be impossible. + << QStringList{"Unknown"}; + + QTest::newRow("Empty packet") << "" + << "Unknown" + << QStringList{"Unknown"}; } void Packet::createPacketFromString() @@ -73,9 +86,9 @@ void Packet::createPacketFromString() QFETCH(QString, expected_header); QFETCH(QStringList, expected_content); - AOPacket packet = AOPacket(incoming_packet); - QCOMPARE(packet.getHeader(), expected_header); - QCOMPARE(packet.getContent(), expected_content); + AOPacket *packet = PacketFactory::createPacket(incoming_packet); + QCOMPARE(packet->getPacketInfo().header, expected_header); + QCOMPARE(packet->getContent(), expected_content); } }