diff --git a/README.md b/README.md index 3681abc..862d627 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,8 @@ Requires Qt >= 5.10, and Qt websockets make ``` -Copyright © scatterflower 2021 \ No newline at end of file +Copyright © scatterflower 2020-2021 + +Copyright © Salanto 2021 + +Copyright © in1tiate 2021 diff --git a/bin/config_sample/areas.ini b/bin/config_sample/areas.ini index 3769b7b..5e687da 100644 --- a/bin/config_sample/areas.ini +++ b/bin/config_sample/areas.ini @@ -1,10 +1,10 @@ -[Basement] +[0:Basement] background=gs4 protected_area=true iniswap_allowed=false evidence_mod=cm -[Courtroom 1] +[1:Courtroom 1] background=gs4 protected_area=false iniswap_allowed=true diff --git a/bin/config_sample/config.ini b/bin/config_sample/config.ini index a33f95d..eaf63ba 100644 --- a/bin/config_sample/config.ini +++ b/bin/config_sample/config.ini @@ -9,6 +9,7 @@ ms_port=27016 port=27016 server_description=This is a placeholder server description. Tell the world of AO who you are here! server_name=An Unnamed Server +motd=MOTD is not set. webao_enable=true webao_port=27017 auth=simple diff --git a/include/aoclient.h b/include/aoclient.h index af7222a..a5442b9 100644 --- a/include/aoclient.h +++ b/include/aoclient.h @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #if QT_VERSION > QT_VERSION_CHECK(5, 10, 0) #include @@ -62,25 +62,6 @@ class AOClient : public QObject { */ ~AOClient(); - /** - * @brief Getter for the hardware ID. - * - * @return The hardware ID. - * - * @see #hwid - */ - QString getHwid(); - - /** - * @brief Setter for the hardware ID. - * - * @param p_hwid A custom string to make into the hardware ID. The hardware ID *won't* become this string, it first - * goes through hashing. - * - * @see #hwid - */ - void setHwid(QString p_hwid); - /** * @brief Getter for the client's IPID. * @@ -89,6 +70,7 @@ class AOClient : public QObject { * @see #ipid */ QString getIpid(); + void calculateIpid(); /** * @brief Getter for the pointer to the server. @@ -164,6 +146,8 @@ class AOClient : public QObject { */ bool global_enabled = true; + bool is_muted = false; + /** * @brief Represents the client's client software, and its version. * @@ -186,18 +170,29 @@ class AOClient : public QObject { /** * @brief The authorisation bitflag, representing what permissions a client can have. + * + * @showinitializer */ QMap ACLFlags { - {"KICK", 1ULL << 0}, - {"BAN", 1ULL << 1}, - {"BGLOCK", 1ULL << 2}, - {"MODIFY_USERS", 1ULL << 3}, - {"CM", 1ULL << 4}, - {"GLOBAL_TIMER", 1ULL << 5}, - {"CHANGE_EVI_MOD", 1ULL << 6}, - {"SUPER", ~0ULL }, + {"NONE", 0ULL }, + {"KICK", 1ULL << 0 }, + {"BAN", 1ULL << 1 }, + {"BGLOCK", 1ULL << 2 }, + {"MODIFY_USERS", 1ULL << 3 }, + {"CM", 1ULL << 4 }, + {"GLOBAL_TIMER", 1ULL << 5 }, + {"EVI_MOD", 1ULL << 6 }, + {"MOTD", 1ULL << 7 }, + {"ANNOUNCE", 1ULL << 8 }, + {"MODCHAT", 1ULL << 9 }, + {"MUTE", 1ULL << 10}, + {"SUPER", ~0ULL }, }; + bool is_shaken; + bool is_disemvoweled; + bool is_gimped; + public slots: /** * @brief A slot for when the client disconnects from the server. @@ -607,7 +602,9 @@ class AOClient : public QObject { * @iscommand */ void cmdHelp(int argc, QStringList argv); - + void cmdMOTD(int argc, QStringList argv); + void cmdAbout(int argc, QStringList argv); + /** * @name Authentication */ @@ -652,6 +649,7 @@ class AOClient : public QObject { * @iscommand */ void cmdAddUser(int argc, QStringList argv); + void cmdRemoveUser(int argc, QStringList argv); /** * @brief Lists the permission of a given user. @@ -901,6 +899,7 @@ class AOClient : public QObject { * @iscommand */ void cmdBan(int argc, QStringList argv); + void cmdUnBan(int argc, QStringList argv); /** * @brief Kicks a client from the server, forcibly severing its connection to the server. @@ -914,6 +913,12 @@ class AOClient : public QObject { * @iscommand */ void cmdKick(int argc, QStringList argv); + void cmdAnnounce(int argc, QStringList argv); + void cmdM(int argc, QStringList argv); + void cmdGM(int argc, QStringList argv); + void cmdMute(int argc, QStringList argv); + void cmdUnmute(int argc, QStringList argv); + void cmdBans(int argc, QStringList argv); ///@} @@ -1026,7 +1031,8 @@ class AOClient : public QObject { * @see AreaData::EvidenceMod */ void cmdEvidenceMod(int argc, QStringList argv); - + void cmdSubTheme(int argc, QStringList argv); + ///@} /** @@ -1173,6 +1179,7 @@ class AOClient : public QObject { * @param Type The type of the dice-rolling being done. */ void diceThrower(int argc, QStringList argv, RollType Type); + long long parseTime(QString input); ///@} @@ -1213,56 +1220,67 @@ class AOClient : public QObject { * See @ref CommandInfo "the type's documentation" for more details. */ const QMap commands { - {"login", {ACLFlags.value("NONE"), 1, &AOClient::cmdLogin }}, - {"getareas", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetAreas }}, - {"getarea", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetArea }}, - {"ban", {ACLFlags.value("BAN"), 2, &AOClient::cmdBan }}, - {"kick", {ACLFlags.value("KICK"), 2, &AOClient::cmdKick }}, - {"changeauth", {ACLFlags.value("SUPER"), 0, &AOClient::cmdChangeAuth }}, - {"rootpass", {ACLFlags.value("SUPER"), 1, &AOClient::cmdSetRootPass }}, - {"background", {ACLFlags.value("NONE"), 1, &AOClient::cmdSetBackground }}, - {"bg", {ACLFlags.value("NONE"), 1, &AOClient::cmdSetBackground }}, - {"bglock", {ACLFlags.value("BGLOCK"), 0, &AOClient::cmdBgLock }}, - {"bgunlock", {ACLFlags.value("BGLOCK"), 0, &AOClient::cmdBgUnlock }}, - {"adduser", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdAddUser }}, - {"listperms", {ACLFlags.value("NONE"), 0, &AOClient::cmdListPerms }}, - {"addperm", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdAddPerms }}, - {"removeperm", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdRemovePerms }}, - {"listusers", {ACLFlags.value("MODIFY_USERS"), 0, &AOClient::cmdListUsers }}, - {"logout", {ACLFlags.value("NONE"), 0, &AOClient::cmdLogout }}, - {"pos", {ACLFlags.value("NONE"), 1, &AOClient::cmdPos }}, - {"g", {ACLFlags.value("NONE"), 1, &AOClient::cmdG }}, - {"need", {ACLFlags.value("NONE"), 1, &AOClient::cmdNeed }}, - {"coinflip", {ACLFlags.value("NONE"), 0, &AOClient::cmdFlip }}, - {"roll", {ACLFlags.value("NONE"), 0, &AOClient::cmdRoll }}, - {"rollp", {ACLFlags.value("NONE"), 0, &AOClient::cmdRollP }}, - {"doc", {ACLFlags.value("NONE"), 0, &AOClient::cmdDoc }}, - {"cleardoc", {ACLFlags.value("NONE"), 0, &AOClient::cmdClearDoc }}, - {"cm", {ACLFlags.value("NONE"), 0, &AOClient::cmdCM }}, - {"uncm", {ACLFlags.value("CM"), 0, &AOClient::cmdUnCM }}, - {"invite", {ACLFlags.value("CM"), 1, &AOClient::cmdInvite }}, - {"uninvite", {ACLFlags.value("CM"), 1, &AOClient::cmdUnInvite }}, - {"lock", {ACLFlags.value("CM"), 0, &AOClient::cmdLock }}, - {"area_lock", {ACLFlags.value("CM"), 0, &AOClient::cmdLock }}, - {"spectatable", {ACLFlags.value("CM"), 0, &AOClient::cmdSpectatable }}, - {"area_spectate", {ACLFlags.value("CM"), 0, &AOClient::cmdSpectatable }}, - {"unlock", {ACLFlags.value("CM"), 0, &AOClient::cmdUnLock }}, - {"area_unlock", {ACLFlags.value("CM"), 0, &AOClient::cmdUnLock }}, - {"timer", {ACLFlags.value("CM"), 0, &AOClient::cmdTimer }}, - {"area", {ACLFlags.value("NONE"), 1, &AOClient::cmdArea }}, - {"play", {ACLFlags.value("CM"), 1, &AOClient::cmdPlay }}, - {"areakick", {ACLFlags.value("CM"), 1, &AOClient::cmdAreaKick }}, - {"area_kick", {ACLFlags.value("CM"), 1, &AOClient::cmdAreaKick }}, - {"randomchar", {ACLFlags.value("NONE"), 0, &AOClient::cmdRandomChar }}, - {"switch", {ACLFlags.value("NONE"), 1, &AOClient::cmdSwitch }}, - {"toggleglobal", {ACLFlags.value("NONE"), 0, &AOClient::cmdToggleGlobal }}, - {"mods", {ACLFlags.value("NONE"), 0, &AOClient::cmdMods }}, - {"help", {ACLFlags.value("NONE"), 0, &AOClient::cmdHelp }}, - {"status", {ACLFlags.value("NONE"), 1, &AOClient::cmdStatus }}, - {"forcepos", {ACLFlags.value("CM"), 2, &AOClient::cmdForcePos }}, - {"currentmusic", {ACLFlags.value("NONE"), 0, &AOClient::cmdCurrentMusic }}, - {"pm", {ACLFlags.value("NONE"), 2, &AOClient::cmdPM }}, - {"evidence_mod", {ACLFlags.value("CHANGE_EVI_MOD"), 1, &AOClient::cmdEvidenceMod }}, + {"login", {ACLFlags.value("NONE"), 1, &AOClient::cmdLogin}}, + {"getareas", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetAreas}}, + {"getarea", {ACLFlags.value("NONE"), 0, &AOClient::cmdGetArea}}, + {"ban", {ACLFlags.value("BAN"), 2, &AOClient::cmdBan}}, + {"kick", {ACLFlags.value("KICK"), 2, &AOClient::cmdKick}}, + {"changeauth", {ACLFlags.value("SUPER"), 0, &AOClient::cmdChangeAuth}}, + {"rootpass", {ACLFlags.value("SUPER"), 1, &AOClient::cmdSetRootPass}}, + {"background", {ACLFlags.value("NONE"), 1, &AOClient::cmdSetBackground}}, + {"bg", {ACLFlags.value("NONE"), 1, &AOClient::cmdSetBackground}}, + {"bglock", {ACLFlags.value("BGLOCK"), 0, &AOClient::cmdBgLock}}, + {"bgunlock", {ACLFlags.value("BGLOCK"), 0, &AOClient::cmdBgUnlock}}, + {"adduser", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdAddUser}}, + {"listperms", {ACLFlags.value("NONE"), 0, &AOClient::cmdListPerms}}, + {"addperm", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdAddPerms}}, + {"removeperm", {ACLFlags.value("MODIFY_USERS"), 2, &AOClient::cmdRemovePerms}}, + {"listusers", {ACLFlags.value("MODIFY_USERS"), 0, &AOClient::cmdListUsers}}, + {"logout", {ACLFlags.value("NONE"), 0, &AOClient::cmdLogout}}, + {"pos", {ACLFlags.value("NONE"), 1, &AOClient::cmdPos}}, + {"g", {ACLFlags.value("NONE"), 1, &AOClient::cmdG}}, + {"need", {ACLFlags.value("NONE"), 1, &AOClient::cmdNeed}}, + {"coinflip", {ACLFlags.value("NONE"), 0, &AOClient::cmdFlip}}, + {"roll", {ACLFlags.value("NONE"), 0, &AOClient::cmdRoll}}, + {"rollp", {ACLFlags.value("NONE"), 0, &AOClient::cmdRollP}}, + {"doc", {ACLFlags.value("NONE"), 0, &AOClient::cmdDoc}}, + {"cleardoc", {ACLFlags.value("NONE"), 0, &AOClient::cmdClearDoc}}, + {"cm", {ACLFlags.value("NONE"), 0, &AOClient::cmdCM}}, + {"uncm", {ACLFlags.value("CM"), 0, &AOClient::cmdUnCM}}, + {"invite", {ACLFlags.value("CM"), 1, &AOClient::cmdInvite}}, + {"uninvite", {ACLFlags.value("CM"), 1, &AOClient::cmdUnInvite}}, + {"lock", {ACLFlags.value("CM"), 0, &AOClient::cmdLock}}, + {"area_lock", {ACLFlags.value("CM"), 0, &AOClient::cmdLock}}, + {"spectatable", {ACLFlags.value("CM"), 0, &AOClient::cmdSpectatable}}, + {"area_spectate", {ACLFlags.value("CM"), 0, &AOClient::cmdSpectatable}}, + {"unlock", {ACLFlags.value("CM"), 0, &AOClient::cmdUnLock}}, + {"area_unlock", {ACLFlags.value("CM"), 0, &AOClient::cmdUnLock}}, + {"timer", {ACLFlags.value("CM"), 0, &AOClient::cmdTimer}}, + {"area", {ACLFlags.value("NONE"), 1, &AOClient::cmdArea}}, + {"play", {ACLFlags.value("CM"), 1, &AOClient::cmdPlay}}, + {"areakick", {ACLFlags.value("CM"), 1, &AOClient::cmdAreaKick}}, + {"area_kick", {ACLFlags.value("CM"), 1, &AOClient::cmdAreaKick}}, + {"randomchar", {ACLFlags.value("NONE"), 0, &AOClient::cmdRandomChar}}, + {"switch", {ACLFlags.value("NONE"), 1, &AOClient::cmdSwitch}}, + {"toggleglobal", {ACLFlags.value("NONE"), 0, &AOClient::cmdToggleGlobal}}, + {"mods", {ACLFlags.value("NONE"), 0, &AOClient::cmdMods}}, + {"help", {ACLFlags.value("NONE"), 0, &AOClient::cmdHelp}}, + {"status", {ACLFlags.value("NONE"), 1, &AOClient::cmdStatus}}, + {"forcepos", {ACLFlags.value("CM"), 2, &AOClient::cmdForcePos}}, + {"currentmusic", {ACLFlags.value("NONE"), 0, &AOClient::cmdCurrentMusic}}, + {"pm", {ACLFlags.value("NONE"), 2, &AOClient::cmdPM}}, + {"evidence_mod", {ACLFlags.value("EVI_MOD"), 1, &AOClient::cmdEvidenceMod}}, + {"motd", {ACLFlags.value("NONE"), 0, &AOClient::cmdMOTD}}, + {"announce", {ACLFlags.value("ANNOUNCE"), 1, &AOClient::cmdAnnounce}}, + {"m", {ACLFlags.value("MODCHAT"), 1, &AOClient::cmdM}}, + {"gm", {ACLFlags.value("MODCHAT"), 1, &AOClient::cmdGM}}, + {"mute", {ACLFlags.value("MUTE"), 1, &AOClient::cmdMute}}, + {"unmute", {ACLFlags.value("MUTE"), 1, &AOClient::cmdUnmute}}, + {"bans", {ACLFlags.value("BAN"), 0, &AOClient::cmdBans}}, + {"unban", {ACLFlags.value("BAN"), 1, &AOClient::cmdUnBan}}, + {"removeuser", {ACLFlags.value("MODIFY_USERS"), 1, &AOClient::cmdRemoveUser}}, + {"subtheme", {ACLFlags.value("CM"), 1, &AOClient::cmdSubTheme}}, + {"about", {ACLFlags.value("NONE"), 0, &AOClient::cmdAbout}}, }; /** diff --git a/include/area_data.h b/include/area_data.h index c6ab3f0..e40cb90 100644 --- a/include/area_data.h +++ b/include/area_data.h @@ -35,15 +35,7 @@ class Logger; class AreaData : public QObject { Q_OBJECT public: - - /** - * @brief Creates an area. - * - * @param p_characters A list of characters that may be selected in the area. - * @param p_name The name of the area. - * @param p_index The index of the area. - */ - AreaData(QStringList p_characters, QString p_name, int p_index); + AreaData(QString p_name, int p_index); /** * @brief The data for evidence in the area. @@ -69,14 +61,7 @@ class AreaData : public QObject { */ int index; - /** - * @brief A map of characters' names and whether they are taken or not. - * - * @details The key for the map the given characters' internal names (i.e., the name of their directory - * in `base/characters/` clientside), while the values are true if they are already taken in the area, - * false if not. - */ - QMap characters_taken; + QList characters_taken; /** * @brief A list of Evidence currently available in the area's court record. diff --git a/include/config_manager.h b/include/config_manager.h index d13d359..8c95af3 100644 --- a/include/config_manager.h +++ b/include/config_manager.h @@ -62,9 +62,9 @@ class ConfigManager { */ struct server_settings { QString ms_ip; //!< The IP address of the master server to establish connection to. - int port; //!< The port of the master server to establish connection to. + int port; //!< The TCP port the server will accept client connections through. int ws_port; //!< The WebSocket port the server will accept client connections through. - int local_port; //!< The TCP port the server will accept client connections through. + int ms_port; //!< The port of the master server to establish connection to. QString name; //!< The name of the server as advertised on the server browser. QString description; //!< The description of the server as advertised on the server browser. bool advertise_server; //!< The server will only be announced to the master server (and thus appear on the master server list) if this is true. diff --git a/include/db_manager.h b/include/db_manager.h index 3aad559..4ecbe2b 100644 --- a/include/db_manager.h +++ b/include/db_manager.h @@ -19,6 +19,7 @@ #define BAN_MANAGER_H #include +#include #include #include #include @@ -89,18 +90,23 @@ public: * or `"Ban reason not found."` if the hardware ID is not actually banned. */ QString getBanReason(QString hdid); + long long getBanDuration(QString hdid); + long long getBanDuration(QHostAddress ip); + int getBanID(QString hdid); + int getBanID(QHostAddress ip); - /** - * @brief Records a ban for the give IPID-IP address-hardware ID combination. - * - * @param ipid The IPID to ban. - * @param ip The IP address to ban. The source should be the same as with the IPID - * (i.e., they should point to the same user). - * @param hdid The hardware ID to ban. Once again, should point to the same user as the IPID and the IP address. - * @param time The number of seconds to ban the target for. - * @param reason The reason the target was banned. - */ - void addBan(QString ipid, QHostAddress ip, QString hdid, unsigned long time, QString reason); + struct BanInfo { + QString ipid; + QHostAddress ip; + QString hdid; + unsigned long time; + QString reason; + long long duration; + }; + QList getRecentBans(); + + void addBan(BanInfo ban); + bool invalidateBan(int id); /** * @brief Creates an authorised user. @@ -116,6 +122,7 @@ public: * @see AOClient::ACLFlags for the potential special permissions a user may have. */ bool createUser(QString username, QString salt, QString password, unsigned long long acl); + bool deleteUser(QString username); /** * @brief Gets the permissions of a given user. diff --git a/include/server.h b/include/server.h index e179e80..4212868 100644 --- a/include/server.h +++ b/include/server.h @@ -80,6 +80,8 @@ class Server : public QObject { */ AOClient* getClient(QString ipid); + QList getClientsByIpid(QString ipid); + /** * @brief Gets a pointer to a client by user ID. * @@ -194,6 +196,7 @@ class Server : public QObject { * @note Unused. getServerName() serves its purpose instead. */ QString server_name; + QString MOTD; /** * @brief The server-wide global timer. diff --git a/src/advertiser.cpp b/src/advertiser.cpp index 4d73017..5921b53 100644 --- a/src/advertiser.cpp +++ b/src/advertiser.cpp @@ -49,7 +49,7 @@ void Advertiser::socketConnected() AOPacket ao_packet("SCC", {concat_ports, name, description, - "akashi v" + QCoreApplication::applicationVersion()}); + "akashi " + QCoreApplication::applicationVersion()}); QByteArray data = ao_packet.toUtf8(); socket->write(data); diff --git a/src/aoclient.cpp b/src/aoclient.cpp index 50ca0c1..b9207bc 100644 --- a/src/aoclient.cpp +++ b/src/aoclient.cpp @@ -48,14 +48,20 @@ void AOClient::clientDisconnected() arup(ARUPType::PLAYER_COUNT, true); } if (current_char != "") { - server->areas[current_area]->characters_taken[current_char] = - false; + server->areas[current_area]->characters_taken.removeAll(server->getCharID(current_char)); server->updateCharsTaken(server->areas[current_area]); } + bool update_locks; for (AreaData* area : server->areas) { area->owners.removeAll(id); area->invited.removeAll(id); + if (area->owners.isEmpty() && area->locked != AreaData::FREE) { + area->locked = AreaData::FREE; + update_locks = true; + } } + if (update_locks) + arup(ARUPType::LOCKED, true); arup(ARUPType::CM, true); } @@ -93,8 +99,7 @@ void AOClient::changeArea(int new_area) } if (current_char != "") { - server->areas[current_area]->characters_taken[current_char] = - false; + server->areas[current_area]->characters_taken.removeAll(server->getCharID(current_char)); server->updateCharsTaken(server->areas[current_area]); } server->areas[new_area]->player_count++; @@ -105,23 +110,23 @@ void AOClient::changeArea(int new_area) sendPacket("HP", {"1", QString::number(server->areas[new_area]->def_hp)}); sendPacket("HP", {"2", QString::number(server->areas[new_area]->pro_hp)}); sendPacket("BN", {server->areas[new_area]->background}); - if (server->areas[current_area]->characters_taken[current_char]) { + if (server->areas[current_area]->characters_taken.contains(server->getCharID(current_char))) { server->updateCharsTaken(server->areas[current_area]); current_char = ""; sendPacket("DONE"); } else { - server->areas[current_area]->characters_taken[current_char] = true; + server->areas[current_area]->characters_taken.append(server->getCharID(current_char)); server->updateCharsTaken(server->areas[current_area]); } for (QTimer* timer : server->areas[current_area]->timers) { int timer_id = server->areas[current_area]->timers.indexOf(timer) + 1; if (timer->isActive()) { - sendPacket("TI", {QString::number(timer_id), QString::number(2)}); - sendPacket("TI", {QString::number(timer_id), QString::number(0), QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(timer->remainingTime())))}); + sendPacket("TI", {QString::number(timer_id), "2"}); + sendPacket("TI", {QString::number(timer_id), "0", QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(timer->remainingTime())))}); } else { - sendPacket("TI", {QString::number(timer_id), QString::number(3)}); + sendPacket("TI", {QString::number(timer_id), "3"}); } } sendServerMessage("You moved to area " + server->area_names[current_area]); @@ -134,7 +139,7 @@ void AOClient::changeCharacter(int char_id) AreaData* area = server->areas[current_area]; if (current_char != "") { - area->characters_taken[current_char] = false; + area->characters_taken.removeAll(server->getCharID(current_char)); } if(char_id > server->characters.length()) @@ -142,11 +147,11 @@ void AOClient::changeCharacter(int char_id) if (char_id >= 0) { QString char_selected = server->characters[char_id]; - bool taken = area->characters_taken.value(char_selected); + bool taken = area->characters_taken.contains(char_id); if (taken || char_selected == "") return; - area->characters_taken[char_selected] = true; + area->characters_taken.append(char_id); current_char = char_selected; } else { @@ -157,24 +162,6 @@ void AOClient::changeCharacter(int char_id) server->updateCharsTaken(area); sendPacket("PV", {QString::number(id), "CID", QString::number(char_id)}); - fullArup(); - if (server->timer->isActive()) { - sendPacket("TI", {QString::number(0), QString::number(2)}); - sendPacket("TI", {QString::number(0), QString::number(0), QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(server->timer->remainingTime())))}); - } - else { - sendPacket("TI", {QString::number(0), QString::number(3)}); - } - for (QTimer* timer : area->timers) { - int timer_id = area->timers.indexOf(timer) + 1; - if (timer->isActive()) { - sendPacket("TI", {QString::number(timer_id), QString::number(2)}); - sendPacket("TI", {QString::number(timer_id), QString::number(0), QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(timer->remainingTime())))}); - } - else { - sendPacket("TI", {QString::number(timer_id), QString::number(3)}); - } - } } void AOClient::changePosition(QString new_pos) @@ -271,22 +258,17 @@ void AOClient::sendPacket(QString header) sendPacket(AOPacket(header, {})); } -QString AOClient::getHwid() { return hwid; } - -void AOClient::setHwid(QString p_hwid) +void AOClient::calculateIpid() { - // TODO: add support for longer hwids? + // TODO: add support for longer ipids? // This reduces the (fairly high) chance of // birthday paradox issues arising. However, // typing more than 8 characters might be a // bit cumbersome. - hwid = p_hwid; - QCryptographicHash hash( - QCryptographicHash::Md5); // Don't need security, just - // hashing for uniqueness - QString concat_ip_id = remote_ip.toString() + p_hwid; - hash.addData(concat_ip_id.toUtf8()); + QCryptographicHash hash(QCryptographicHash::Md5); // Don't need security, just hashing for uniqueness + + hash.addData(remote_ip.toString().toUtf8()); ipid = hash.result().toHex().right(8); // Use the last 8 characters (4 bytes) } diff --git a/src/area_data.cpp b/src/area_data.cpp index 2d4893c..f30b36d 100644 --- a/src/area_data.cpp +++ b/src/area_data.cpp @@ -17,8 +17,7 @@ ////////////////////////////////////////////////////////////////////////////////////// #include "include/area_data.h" -AreaData::AreaData(QStringList characters, QString p_name, int p_index) : - name(p_name), +AreaData::AreaData(QString p_name, int p_index) : index(p_index), player_count(0), status(IDLE), @@ -27,9 +26,9 @@ AreaData::AreaData(QStringList characters, QString p_name, int p_index) : def_hp(10), pro_hp(10) { - for (QString cur_char : characters) { - characters_taken.insert(cur_char, false); - } + QStringList name_split = p_name.split(":"); + name_split.removeFirst(); + name = name_split.join(":"); QSettings areas_ini("config/areas.ini", QSettings::IniFormat); areas_ini.beginGroup(p_name); background = areas_ini.value("background", "gs4").toString(); diff --git a/src/commands.cpp b/src/commands.cpp index 4cb0c06..2455016 100644 --- a/src/commands.cpp +++ b/src/commands.cpp @@ -1,6 +1,6 @@ ////////////////////////////////////////////////////////////////////////////////////// // akashi - a server for Attorney Online 2 // -// Copyright (C) 2020 scatterflower // +// 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 // @@ -42,10 +42,12 @@ void AOClient::cmdLogin(int argc, QStringList argv) sendServerMessage("No modpass is set! Please set a modpass before authenticating."); } else if(argv[0] == modpass) { - sendServerMessage("Logged in as a moderator."); // This string has to be exactly this, because it is hardcoded in the client + sendPacket("AUTH", {"1"}); // Client: "You were granted the Disable Modcalls button." + sendServerMessage("Logged in as a moderator."); // pre-2.9.1 clients are hardcoded to display the mod UI when this string is sent in OOC authenticated = true; - } + } else { + sendPacket("AUTH", {"0"}); // Client: "Login unsuccessful." sendServerMessage("Incorrect password."); } server->areas.value(current_area)->logger->logLogin(this, authenticated, "moderator"); @@ -60,10 +62,13 @@ void AOClient::cmdLogin(int argc, QStringList argv) if (server->db_manager->authenticate(username, password)) { moderator_name = username; authenticated = true; - sendServerMessage("Logged in as a moderator."); + sendPacket("AUTH", {"1"}); // Client: "You were granted the Disable Modcalls button." + if (version.release <= 2 && version.major <= 9 && version.minor <= 0) + sendServerMessage("Logged in as a moderator."); // pre-2.9.1 clients are hardcoded to display the mod UI when this string is sent in OOC sendServerMessage("Welcome, " + username); } else { + sendPacket("AUTH", {"0"}); // Client: "Login unsuccessful." sendServerMessage("Incorrect password."); } server->areas.value(current_area)->logger->logLogin(this, authenticated, username); @@ -89,33 +94,72 @@ void AOClient::cmdGetArea(int argc, QStringList argv) void AOClient::cmdBan(int argc, QStringList argv) { - QString target_ipid = argv[0]; - QHostAddress ip; - QString hdid; - unsigned long time = QDateTime::currentDateTime().toTime_t(); - QString reason = argv[1]; + QString args_str = argv[1]; + if (argc > 2) { + for (int i = 2; i < argc; i++) + args_str += " " + argv[i]; + } + + DBManager::BanInfo ban; + + QRegularExpression quoteMatcher("['\"](.+?)[\"']"); + QRegularExpressionMatchIterator matches = quoteMatcher.globalMatch(args_str); + QList unquoted_args; + while (matches.hasNext()) { + QRegularExpressionMatch match = matches.next(); + unquoted_args.append(match.captured(1)); + } + + QString duration = "perma"; + + if (unquoted_args.length() < 1) { + sendServerMessage("Invalid syntax. Usage:\n/ban \"\" \"\""); + return; + } + + ban.reason = unquoted_args.at(0); + if (unquoted_args.length() > 1) + duration = unquoted_args.at(1); + + long long duration_seconds = 0; + if (duration == "perma") + duration_seconds = -2; + else + duration_seconds = parseTime(duration); + + if (duration_seconds == -1) { + sendServerMessage("Invalid time format. Format example: 1h30m"); + return; + } + + ban.duration = duration_seconds; + + ban.ipid = argv[0]; + ban.time = QDateTime::currentDateTime().toSecsSinceEpoch(); bool ban_logged = false; + int kick_counter = 0; if (argc > 2) { for (int i = 2; i < argv.length(); i++) { - reason += " " + argv[i]; + ban.reason += " " + argv[i]; } } - for (AOClient* client : server->clients) { - if (client->getIpid() == target_ipid) { - if (!ban_logged) { - ip = client->remote_ip; - hdid = client->hwid; - server->db_manager->addBan(target_ipid, ip, hdid, time, reason); - sendServerMessage("Banned user with ipid " + target_ipid + " for reason: " + reason); - ban_logged = true; - } - client->sendPacket("KB", {reason}); - client->socket->close(); + for (AOClient* client : server->getClientsByIpid(ban.ipid)) { + if (!ban_logged) { + ban.ip = client->remote_ip; + ban.hdid = client->hwid; + server->db_manager->addBan(ban); + sendServerMessage("Banned user with ipid " + ban.ipid + " for reason: " + ban.reason); + ban_logged = true; } + client->sendPacket("KB", {ban.reason + "\nID: " + QString::number(server->db_manager->getBanID(ban.ip)) + "\nUntil: " + QDateTime::fromSecsSinceEpoch(ban.time).addSecs(ban.duration).toString("dd.MM.yyyy, hh:mm")}); + client->socket->close(); + kick_counter++; } + if (kick_counter > 1) + sendServerMessage("Kicked " + QString::number(kick_counter) + " clients with matching ipids."); if (!ban_logged) sendServerMessage("User with ipid not found!"); } @@ -124,7 +168,7 @@ void AOClient::cmdKick(int argc, QStringList argv) { QString target_ipid = argv[0]; QString reason = argv[1]; - bool did_kick = false; + int kick_counter = 0; if (argc > 2) { for (int i = 2; i < argv.length(); i++) { @@ -132,16 +176,14 @@ void AOClient::cmdKick(int argc, QStringList argv) } } - for (AOClient* client : server->clients) { - if (client->getIpid() == target_ipid) { - client->sendPacket("KK", {reason}); - client->socket->close(); - did_kick = true; - } + for (AOClient* client : server->getClientsByIpid(target_ipid)) { + client->sendPacket("KK", {reason}); + client->socket->close(); + kick_counter++; } - if (did_kick) - sendServerMessage("Kicked user with ipid " + target_ipid + " for reason: " + reason); + if (kick_counter > 0) + sendServerMessage("Kicked " + QString::number(kick_counter) + " client(s) with ipid " + target_ipid + " for reason: " + reason); else sendServerMessage("User with ipid not found!"); } @@ -232,6 +274,14 @@ void AOClient::cmdAddUser(int argc, QStringList argv) sendServerMessage("Unable to create user " + argv[0] + ".\nDoes a user with that name already exist?"); } +void AOClient::cmdRemoveUser(int argc, QStringList argv) +{ + if (server->db_manager->deleteUser(argv[0])) + sendServerMessage("Successfully removed user " + argv[0] + "."); + else + sendServerMessage("Unable to remove user " + argv[0] + ".\nDoes it exist?"); +} + void AOClient::cmdListPerms(int argc, QStringList argv) { unsigned long long user_acl = server->db_manager->getACL(moderator_name); @@ -359,7 +409,7 @@ void AOClient::cmdLogout(int argc, QStringList argv) } authenticated = false; moderator_name = ""; - sendServerMessage("You have been logged out."); + sendPacket("AUTH", {"-1"}); // Client: "You were logged out." } void AOClient::cmdPos(int argc, QStringList argv) @@ -505,8 +555,13 @@ void AOClient::cmdUnCM(int argc, QStringList argv) area->invited.removeAll(id); sendServerMessage("You are no longer CM in this area."); arup(ARUPType::CM, true); - if (area->owners.isEmpty()) + if (area->owners.isEmpty()) { area->invited.clear(); + if (area->locked != AreaData::FREE) { + area->locked = AreaData::FREE; + arup(ARUPType::LOCKED, true); + } + } } void AOClient::cmdInvite(int argc, QStringList argv) @@ -657,28 +712,28 @@ void AOClient::cmdTimer(int argc, QStringList argv) requested_timer->setInterval(QTime(0,0).msecsTo(requested_time)); requested_timer->start(); sendServerMessage("Set timer " + QString::number(timer_id) + " to " + argv[1] + "."); - sendPacket("TI", {QString::number(timer_id), QString::number(2)}); - sendPacket("TI", {QString::number(timer_id), QString::number(0), QString::number(QTime(0,0).msecsTo(requested_time))}); + sendPacket("TI", {QString::number(timer_id), "2"}); // Show the timer + sendPacket("TI", {QString::number(timer_id), "0", QString::number(QTime(0,0).msecsTo(requested_time))}); return; } else { if (argv[1] == "start") { requested_timer->start(); sendServerMessage("Started timer " + QString::number(timer_id) + "."); - sendPacket("TI", {QString::number(timer_id), QString::number(2)}); - sendPacket("TI", {QString::number(timer_id), QString::number(0), QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(requested_timer->remainingTime())))}); + sendPacket("TI", {QString::number(timer_id), "2"}); // Show the timer + sendPacket("TI", {QString::number(timer_id), "0", QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(requested_timer->remainingTime())))}); } else if (argv[1] == "pause" || argv[1] == "stop") { requested_timer->setInterval(requested_timer->remainingTime()); requested_timer->stop(); sendServerMessage("Stopped timer " + QString::number(timer_id) + "."); - sendPacket("TI", {QString::number(timer_id), QString::number(1), QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(requested_timer->interval())))}); + sendPacket("TI", {QString::number(timer_id), "1", QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(requested_timer->interval())))}); } else if (argv[1] == "hide" || argv[1] == "unset") { requested_timer->setInterval(0); requested_timer->stop(); sendServerMessage("Hid timer " + QString::number(timer_id) + "."); - sendPacket("TI", {QString::number(timer_id), QString::number(3)}); + sendPacket("TI", {QString::number(timer_id), "3"}); // Hide the timer } } } @@ -722,7 +777,8 @@ void AOClient::cmdPlay(int argc, QStringList argv) QString song = argv.join(" "); area->current_music = song; area->music_played_by = showname; - sendPacket("MC", {song, QString::number(server->getCharID(current_char)), showname, "1", "0"}); + AOPacket music_change("MC", {song, QString::number(server->getCharID(current_char)), showname, "1", "0"}); + server->broadcast(music_change, current_area); } void AOClient::cmdAreaKick(int argc, QStringList argv) @@ -776,8 +832,8 @@ void AOClient::cmdMods(int argc, QStringList argv) if (client->authenticated) { entries << "---"; if (auth_type != "simple") - entries << "Moderator: " + moderator_name; - entries << "OOC name: " + ooc_name; + entries << "Moderator: " + client->moderator_name; + entries << "OOC name: " + client->ooc_name; entries << "ID: " + QString::number(client->id); entries << "Area: " + QString::number(client->current_area); entries << "Character: " + client->current_char; @@ -852,6 +908,133 @@ void AOClient::cmdPM(int arc, QStringList argv) target_client->sendServerMessage("Message from " + ooc_name + " (" + QString::number(id) + "): " + message); } +void AOClient::cmdMOTD(int argc, QStringList argv) +{ + if (argc == 0) { + sendServerMessage("=== MOTD ===\r\n" + server->MOTD + "\r\n============="); + } + else if (argc > 0) { + if (checkAuth(ACLFlags.value("MOTD"))) { + QString MOTD = argv.join(" "); + server->MOTD = MOTD; + sendServerMessage("MOTD has been changed."); + } + else { + sendServerMessage("You do not have permission to change the MOTD"); + } + } +} + +void AOClient::cmdAnnounce(int argc, QStringList argv) +{ + sendServerBroadcast("=== Announcement ===\r\n" + argv.join(" ") + "\r\n============="); +} + +void AOClient::cmdM(int argc, QStringList argv) +{ + QString sender_name = ooc_name; + QString sender_message = argv.join(" "); + for (AOClient* client : server->clients) { + if (client->checkAuth(ACLFlags.value("MODCHAT"))) + client->sendPacket("CT", {"[M]" + sender_name, sender_message}); + } + return; +} + +void AOClient::cmdGM(int argc, QStringList argv) +{ + QString sender_name = ooc_name; + QString sender_area = server->area_names.value(current_area); + QString sender_message = argv.join(" "); + for (AOClient* client : server->clients) { + if (client->global_enabled) { + client->sendPacket("CT", {"[G][" + sender_area + "]" + "["+sender_name+"][M]", sender_message}); + } + } +} + +void AOClient::cmdMute(int argc, QStringList argv) +{ + bool conv_ok = false; + int uid = argv[0].toInt(&conv_ok); + if (!conv_ok) { + sendServerMessage("Invalid user ID."); + return; + } + + if (server->getClientByID(uid)->is_muted) + sendServerMessage("That player is already muted!"); + else + sendServerMessage("Muted player."); + server->getClientByID(uid)->is_muted = true; +} + +void AOClient::cmdUnmute(int argc, QStringList argv) +{ + bool conv_ok = false; + int uid = argv[0].toInt(&conv_ok); + if (!conv_ok) { + sendServerMessage("Invalid user ID."); + return; + } + + if (!server->getClientByID(uid)->is_muted) + sendServerMessage("That player is already unmuted!"); + else + sendServerMessage("Unmuted player."); + server->getClientByID(uid)->is_muted = false; +} + +void AOClient::cmdBans(int argc, QStringList argv) +{ + QStringList recent_bans; + recent_bans << "Last 5 bans:"; + recent_bans << "-----"; + for (DBManager::BanInfo ban : server->db_manager->getRecentBans()) { + QString banned_until; + if (ban.duration == -2) + banned_until = "The heat death of the universe"; + else + banned_until = QDateTime::fromSecsSinceEpoch(ban.time).addSecs(ban.duration).toString("dd.MM.yyyy, hh:mm"); + recent_bans << "Affected IPID: " + ban.ipid; + recent_bans << "Affected HDID: " + ban.hdid; + recent_bans << "Reason for ban: " + ban.reason; + recent_bans << "Date of ban: " + QDateTime::fromSecsSinceEpoch(ban.time).toString("dd.MM.yyyy, hh:mm"); + recent_bans << "Ban lasts until: " + banned_until; + recent_bans << "-----"; + } + sendServerMessage(recent_bans.join("\n")); +} + +void AOClient::cmdUnBan(int argc, QStringList argv) +{ + bool ok; + int target_ban = argv[0].toInt(&ok); + if (!ok) { + sendServerMessage("Invalid ban ID."); + return; + } + else if (server->db_manager->invalidateBan(target_ban)) + sendServerMessage("Successfully invalidated ban " + argv[0] + "."); + else + sendServerMessage("Couldn't invalidate ban " + argv[0] + ", are you sure it exists?"); +} + +void AOClient::cmdSubTheme(int argc, QStringList argv) +{ + QString subtheme = argv.join(" "); + for (AOClient* client : server->clients) { + if (client->current_area == current_area) + client->sendPacket("ST", {subtheme, "1"}); + } + sendServerMessageArea("Subtheme was set to " + subtheme); +} + +void AOClient::cmdAbout(int argc, QStringList argv) +{ + sendPacket("CT", {"The akashi dev team", "Thank you for using akashi! Made with love by scatterflower, with help from in1tiate and Salanto. akashi " + QCoreApplication::applicationVersion()}); +} + QStringList AOClient::buildAreaList(int area_idx) { QStringList entries; @@ -869,7 +1052,7 @@ QStringList AOClient::buildAreaList(int area_idx) default: break; } - entries.append("[" + QString::number(area->player_count) + " users][" + area->status + "]"); + entries.append("[" + QString::number(area->player_count) + " users][" + QVariant::fromValue(area->status).toString().replace("_", "-") + "]"); for (AOClient* client : server->clients) { if (client->current_area == area_idx && client->joined) { QString char_entry = "[" + QString::number(client->id) + "] " + client->current_char; @@ -968,3 +1151,45 @@ QString AOClient::getAreaTimer(int area_idx, QTimer* timer) } } +long long AOClient::parseTime(QString input) +{ + QRegularExpression regex("(?:(?:(?.*?)y)*(?:(?.*?)w)*(?:(?.*?)d)*(?:(?
.*?)h)*(?:(?.*?)m)*(?:(?.*?)s)*)"); + QRegularExpressionMatch match = regex.match(input); + QString str_year, str_week, str_hour, str_day, str_minute, str_second; + int year, week, day, hour, minute, second; + + str_year = match.captured("year"); + str_week = match.captured("week"); + str_day = match.captured("day"); + str_hour = match.captured("hr"); + str_minute = match.captured("min"); + str_second = match.captured("sec"); + + bool is_well_formed = false; + QString concat_str(str_year + str_week + str_day + str_hour + str_minute + str_second); + concat_str.toInt(&is_well_formed); + + if (!is_well_formed) { + return -1; + } + + year = str_year.toInt(); + week = str_week.toInt(); + day = str_day.toInt(); + hour = str_hour.toInt(); + minute = str_minute.toInt(); + second = str_second.toInt(); + + long long total = 0; + total += 31622400 * year; + total += 604800 * week; + total += 86400 * day; + total += 3600 * hour; + total += 60 * minute; + total += second; + + if (total < 0) + return -1; + + return total; +} diff --git a/src/config_manager.cpp b/src/config_manager.cpp index 57f5f7f..69de3cf 100644 --- a/src/config_manager.cpp +++ b/src/config_manager.cpp @@ -137,8 +137,8 @@ bool ConfigManager::loadServerSettings(server_settings* settings) config.value("port", "27016").toInt(&port_conversion_success); settings->ws_port = config.value("webao_port", "27017").toInt(&ws_port_conversion_success); - settings->local_port = - config.value("port", "27016").toInt(&local_port_conversion_success); + settings->ms_port = + config.value("ms_port", "27016").toInt(&local_port_conversion_success); settings->name = config.value("server_name", "My First Server").toString(); settings->description = config.value("server_description", "This is my flashy new server") diff --git a/src/db_manager.cpp b/src/db_manager.cpp index f23f20e..bd2ba32 100644 --- a/src/db_manager.cpp +++ b/src/db_manager.cpp @@ -24,32 +24,52 @@ DBManager::DBManager() : db.setDatabaseName("config/akashi.db"); if (!db.open()) qCritical() << "Database Error:" << db.lastError(); - QSqlQuery create_ban_table("CREATE TABLE IF NOT EXISTS bans ('ID' INTEGER, 'IPID' TEXT, 'HDID' TEXT, 'IP' TEXT, 'TIME' INTEGER, 'REASON' TEXT, PRIMARY KEY('ID' AUTOINCREMENT))"); + QSqlQuery create_ban_table("CREATE TABLE IF NOT EXISTS bans ('ID' INTEGER, 'IPID' TEXT, 'HDID' TEXT, 'IP' TEXT, 'TIME' INTEGER, 'REASON' TEXT, 'DURATION' INTEGER, PRIMARY KEY('ID' AUTOINCREMENT))"); QSqlQuery create_user_table("CREATE TABLE IF NOT EXISTS users ('ID' INTEGER, 'USERNAME' TEXT, 'SALT' TEXT, 'PASSWORD' TEXT, 'ACL' TEXT, PRIMARY KEY('ID' AUTOINCREMENT))"); } bool DBManager::isIPBanned(QHostAddress ip) { QSqlQuery query; - query.prepare("SELECT ID FROM BANS WHERE IP = ?"); + query.prepare("SELECT TIME FROM BANS WHERE IP = ? ORDER BY TIME DESC"); query.addBindValue(ip.toString()); query.exec(); - return query.first(); + if (query.first()) { + long long duration = getBanDuration(ip); + long long ban_time = query.value(0).toLongLong(); + if (duration == -2) + return true; + long long current_time = QDateTime::currentDateTime().toSecsSinceEpoch(); + if (ban_time + duration > current_time) + return true; + else return false; + } + else return false; } bool DBManager::isHDIDBanned(QString hdid) { QSqlQuery query; - query.prepare("SELECT ID FROM BANS WHERE HDID = ?"); + query.prepare("SELECT TIME FROM BANS WHERE HDID = ? ORDER BY TIME DESC"); query.addBindValue(hdid); query.exec(); - return query.first(); + if (query.first()) { + long long duration = getBanDuration(hdid); + long long ban_time = query.value(0).toLongLong(); + if (duration == -2) + return true; + long long current_time = QDateTime::currentDateTime().toSecsSinceEpoch(); + if (ban_time + duration > current_time) + return true; + else return false; + } + else return false; } QString DBManager::getBanReason(QHostAddress ip) { QSqlQuery query; - query.prepare("SELECT REASON FROM BANS WHERE IP = ?"); + query.prepare("SELECT REASON FROM BANS WHERE IP = ? ORDER BY TIME DESC"); query.addBindValue(ip.toString()); query.exec(); if (query.first()) { @@ -63,7 +83,7 @@ QString DBManager::getBanReason(QHostAddress ip) QString DBManager::getBanReason(QString hdid) { QSqlQuery query; - query.prepare("SELECT REASON FROM BANS WHERE HDID = ?"); + query.prepare("SELECT REASON FROM BANS WHERE HDID = ? ORDER BY TIME DESC"); query.addBindValue(hdid); query.exec(); if (query.first()) { @@ -74,19 +94,116 @@ QString DBManager::getBanReason(QString hdid) } } -void DBManager::addBan(QString ipid, QHostAddress ip, QString hdid, unsigned long time, QString reason) +long long DBManager::getBanDuration(QString hdid) { QSqlQuery query; - query.prepare("INSERT INTO BANS(IPID, HDID, IP, TIME, REASON) VALUES(?, ?, ?, ?, ?)"); - query.addBindValue(ipid); + query.prepare("SELECT DURATION FROM BANS WHERE HDID = ? ORDER BY TIME DESC"); query.addBindValue(hdid); + query.exec(); + if (query.first()) { + return query.value(0).toLongLong(); + } + else { + return -1; + } +} + +long long DBManager::getBanDuration(QHostAddress ip) +{ + QSqlQuery query; + query.prepare("SELECT DURATION FROM BANS WHERE IP = ? ORDER BY TIME DESC"); query.addBindValue(ip.toString()); - query.addBindValue(QString::number(time)); - query.addBindValue(reason); + query.exec(); + if (query.first()) { + return query.value(0).toLongLong(); + } + else { + return -1; + } +} + + +int DBManager::getBanID(QString hdid) +{ + QSqlQuery query; + query.prepare("SELECT ID FROM BANS WHERE HDID = ?"); + query.addBindValue(hdid); + query.exec(); + if (query.first()) { + return query.value(0).toInt(); + } + else { + return -1; + } +} + + +int DBManager::getBanID(QHostAddress ip) +{ + QSqlQuery query; + query.prepare("SELECT ID FROM BANS WHERE IP = ?"); + query.addBindValue(ip.toString()); + query.exec(); + if (query.first()) { + return query.value(0).toInt(); + } + else { + return -1; + } +} + +QList DBManager::getRecentBans() +{ + QList return_list; + QSqlQuery query; + query.prepare("SELECT TOP(5) * FROM BANS ORDER BY TIME DESC"); + query.setForwardOnly(true); + query.exec(); + while (query.next()) { + BanInfo ban; + ban.ipid = query.value(0).toString(); + ban.hdid = query.value(1).toString(); + ban.ip = QHostAddress(query.value(2).toString()); + ban.time = static_cast(query.value(3).toULongLong()); + ban.reason = query.value(4).toString(); + ban.duration = query.value(5).toLongLong(); + return_list.append(ban); + } + std::reverse(return_list.begin(), return_list.end()); + return return_list; +} + +void DBManager::addBan(BanInfo ban) +{ + QSqlQuery query; + query.prepare("INSERT INTO BANS(IPID, HDID, IP, TIME, REASON, DURATION) VALUES(?, ?, ?, ?, ?, ?)"); + query.addBindValue(ban.ipid); + query.addBindValue(ban.hdid); + query.addBindValue(ban.ip.toString()); + query.addBindValue(QString::number(ban.time)); + query.addBindValue(ban.reason); + query.addBindValue(ban.duration); if (!query.exec()) qDebug() << "SQL Error:" << query.lastError().text(); } +bool DBManager::invalidateBan(int id) +{ + QSqlQuery ban_exists; + ban_exists.prepare("SELECT DURATION FROM bans WHERE ID = ?"); + ban_exists.addBindValue(id); + ban_exists.exec(); + + if (ban_exists.first()) + return false; + + QSqlQuery query; + query.prepare("UPDATE bans SET DURATION = 0 WHERE ID = ?"); + query.addBindValue(id); + query.exec(); + return true; +} + bool DBManager::createUser(QString username, QString salt, QString password, unsigned long long acl) { QSqlQuery username_exists; @@ -115,6 +232,23 @@ bool DBManager::createUser(QString username, QString salt, QString password, uns return true; } +bool DBManager::deleteUser(QString username) +{ + QSqlQuery username_exists; + username_exists.prepare("SELECT ACL FROM users WHERE USERNAME = ?"); + username_exists.addBindValue(username); + username_exists.exec(); + + if (username_exists.first()) + return false; + + QSqlQuery query; + query.prepare("DELETE FROM users WHERE USERNAME = ?"); + username_exists.addBindValue(username); + username_exists.exec(); + return true; +} + unsigned long long DBManager::getACL(QString moderator_name) { if (moderator_name == "") diff --git a/src/main.cpp b/src/main.cpp index 630965b..8560141 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -36,7 +36,7 @@ int main(int argc, char* argv[]) { QCoreApplication app(argc, argv); QCoreApplication::setApplicationName("akashi"); - QCoreApplication::setApplicationVersion("0.0.1"); + QCoreApplication::setApplicationVersion("apricot r2 prerelease"); std::atexit(cleanup); ConfigManager config_manager; @@ -55,8 +55,8 @@ int main(int argc, char* argv[]) else { if (settings.advertise_server) { advertiser = - new Advertiser(settings.ms_ip, settings.port, - settings.ws_port, settings.local_port, + new Advertiser(settings.ms_ip, settings.ms_port, + settings.ws_port, settings.port, settings.name, settings.description); advertiser->contactMasterServer(); } diff --git a/src/packets.cpp b/src/packets.cpp index 729d30f..80e89ce 100644 --- a/src/packets.cpp +++ b/src/packets.cpp @@ -26,9 +26,9 @@ void AOClient::pktDefault(AreaData* area, int argc, QStringList argv, AOPacket p void AOClient::pktHardwareId(AreaData* area, int argc, QStringList argv, AOPacket packet) { - setHwid(argv[0]); - if(server->db_manager->isHDIDBanned(getHwid())) { - sendPacket("BD", {server->db_manager->getBanReason(getHwid())}); + hwid = argv[0]; + if(server->db_manager->isHDIDBanned(hwid)) { + sendPacket("BD", {server->db_manager->getBanReason(hwid)}); socket->close(); return; } @@ -46,37 +46,23 @@ void AOClient::pktSoftwareId(AreaData* area, int argc, QStringList argv, AOPacke // The only ones that are critical to ensuring the server works are // "noencryption" and "fastloading" QStringList feature_list = { - "noencryption", "yellowtext", "prezoom", - "flipping", "customobjections", "fastloading", - "deskmod", "evidence", "cccc_ic_support", - "arup", "casing_alerts", "modcall_reason", - "looping_sfx", "additive", "effects", - "y_offset" + "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" }; - // Extremely cursed client version string validation - // Ideally version strings should be X.X.X but it can be literally anything - // so we have to be super careful version.string = argv[1]; - QStringList version_raw = version.string.split("."); - bool ok; - int release_version = version_raw[0].toInt(&ok); - if (ok && version_raw.size() >= 1) - version.release = release_version; - if (ok && version_raw.size() >= 2) { - int major_version = version_raw[1].toInt(&ok); - if (ok) - version.major = major_version; + QRegularExpression rx("\\b(\\d+)\\.(\\d+)\\.(\\d+)\\b"); // matches X.X.X (e.g. 2.9.0, 2.4.10, etc.) + QRegularExpressionMatch match = rx.match(version.string); + if (match.hasMatch()) { + version.release = match.captured(1).toInt(); + version.major = match.captured(2).toInt(); + version.minor = match.captured(3).toInt(); } - if (ok && version_raw.size() >= 3) { - int minor_version = version_raw[2].toInt(&ok); - if (ok) - version.minor = minor_version; - } - - - sendPacket("PN", {QString::number(server->player_count), max_players}); sendPacket("FL", feature_list); @@ -102,7 +88,7 @@ void AOClient::pktRequestMusic(AreaData* area, int argc, QStringList argv, AOPac void AOClient::pktLoadingDone(AreaData* area, int argc, QStringList argv, AOPacket packet) { - if (getHwid() == "") { + if (hwid == "") { // No early connecting! socket->close(); return; @@ -116,16 +102,37 @@ void AOClient::pktLoadingDone(AreaData* area, int argc, QStringList argv, AOPack area->player_count++; joined = true; server->updateCharsTaken(area); - fullArup(); // Give client all the area data + arup(ARUPType::PLAYER_COUNT, true); // Tell everyone there is a new player sendEvidenceList(area); sendPacket("HP", {"1", QString::number(area->def_hp)}); sendPacket("HP", {"2", QString::number(area->pro_hp)}); sendPacket("FA", server->area_names); - sendPacket("BN", {area->background}); sendPacket("OPPASS", {"DEADBEEF"}); sendPacket("DONE"); + sendPacket("BN", {area->background}); + + sendServerMessage("=== MOTD ===\r\n" + server->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"}); + } + for (QTimer* timer : area->timers) { + int timer_id = area->timers.indexOf(timer) + 1; + if (timer->isActive()) { + sendPacket("TI", {QString::number(timer_id), "2"}); + sendPacket("TI", {QString::number(timer_id), "0", QString::number(QTime(0,0).msecsTo(QTime(0,0).addMSecs(timer->remainingTime())))}); + } + else { + sendPacket("TI", {QString::number(timer_id), "3"}); + } + } } void AOClient::pktCharPassword(AreaData* area, int argc, QStringList argv, AOPacket packet) @@ -147,6 +154,11 @@ void AOClient::pktSelectChar(AreaData* area, int argc, QStringList argv, AOPacke void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket packet) { + if (is_muted) { + sendServerMessage("You cannot speak while muted."); + return; + } + AOPacket validated_packet = validateIcPacket(packet); if (validated_packet.header == "INVALID") return; @@ -161,8 +173,7 @@ void AOClient::pktIcChat(AreaData* area, int argc, QStringList argv, AOPacket pa void AOClient::pktOocChat(AreaData* area, int argc, QStringList argv, AOPacket packet) { ooc_name = dezalgo(argv[0]).replace(QRegExp("\\[|\\]|\\{|\\}|\\#|\\$|\\%|\\&"), ""); // no fucky wucky shit here - - if (ooc_name == server->getServerName()) // impersonation prevention + if (ooc_name.isEmpty() || ooc_name == server->getServerName()) // impersonation & empty name protection return; QString message = dezalgo(argv[1]); @@ -203,9 +214,19 @@ void AOClient::pktChangeMusic(AreaData* area, int argc, QStringList argv, AOPack for (QString song : server->music_list) { if (song == argument || song == "~stop.mp3") { // ~stop.mp3 is a dummy track used by 2.9+ // We have a song here - AOPacket music_change("MC", {song, argv[1], argv[2], "1", "0", argv[3]}); - area->current_music = song; - area->music_played_by = argv[2]; + QString effects; + if (argc >= 4) + effects = argv[3]; + else + effects = "0"; + QString final_song; + if (!argument.contains(".")) + final_song = "~stop.mp3"; + else + final_song = argument; + AOPacket music_change("MC", {final_song, argv[1], showname, "1", "0", effects}); + area->current_music = final_song; + area->music_played_by = showname; server->broadcast(music_change, current_area); return; } @@ -254,6 +275,7 @@ void AOClient::pktWebSocketIp(AreaData* area, int argc, QStringList argv, AOPack qDebug() << "ws ip set to" << argv[0]; #endif remote_ip = QHostAddress(argv[0]); + calculateIpid(); } } @@ -360,15 +382,14 @@ AOPacket AOClient::validateIcPacket(AOPacket packet) incoming_args.append(QVariant(arg)); } - // message type - if (incoming_args[0].toInt() == 1) - args.append("1"); - else if (incoming_args[0].toInt() == 0) { - if (incoming_args[0].toString() == "chat") - args.append("chat"); - else - args.append("0"); + // desk modifier + QStringList allowed_desk_mods; + allowed_desk_mods << "chat" << "0" << "1" << "2" << "3" << "4" << "5"; + if (allowed_desk_mods.contains(incoming_args[0].toString())) { + args.append(incoming_args[0].toString()); } + else + return invalid; // preanim args.append(incoming_args[1].toString()); diff --git a/src/server.cpp b/src/server.cpp index db8ed5a..148dce2 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -51,6 +51,8 @@ void Server::start() qDebug() << "Server listening on" << port; } + MOTD = config.value("motd","MOTD is not set.").toString(); + proxy = new WSProxy(port, ws_port, this); if(ws_port != -1) proxy->start(); @@ -79,10 +81,20 @@ void Server::start() bg_file.close(); QSettings areas_ini("config/areas.ini", QSettings::IniFormat); - area_names = areas_ini.childGroups(); - for (int i = 0; i < area_names.length(); i++) { - QString area_name = area_names[i]; - areas.insert(i, new AreaData(characters, area_name, i)); + area_names = areas_ini.childGroups(); // invisibly does a lexicographical sort, because Qt is great like that + std::sort(area_names.begin(), area_names.end(), [] (const QString &a, const QString &b) {return a.split(":")[0].toInt() < b.split(":")[0].toInt();}); + QStringList sanitized_area_names; + QStringList raw_area_names = area_names; + for (QString area_name : area_names) { + QStringList name_split = area_name.split(":"); + name_split.removeFirst(); + QString area_name_sanitized = name_split.join(":"); + sanitized_area_names.append(area_name_sanitized); + } + area_names = sanitized_area_names; + for (int i = 0; i < raw_area_names.length(); i++) { + QString area_name = raw_area_names[i]; + areas.insert(i, new AreaData(area_name, i)); } } @@ -130,8 +142,8 @@ void Server::clientConnected() void Server::updateCharsTaken(AreaData* area) { QStringList chars_taken; - for (QString cur_char : area->characters_taken.keys()) { - chars_taken.append(area->characters_taken.value(cur_char) + for (QString cur_char : characters) { + chars_taken.append(area->characters_taken.contains(getCharID(cur_char)) ? QStringLiteral("-1") : QStringLiteral("0")); } @@ -173,13 +185,14 @@ int Server::getDiceValue(QString value_type) return value; } -AOClient* Server::getClient(QString ipid) +QList Server::getClientsByIpid(QString ipid) { + QList return_clients; for (AOClient* client : clients) { if (client->getIpid() == ipid) - return client; + return_clients.append(client); } - return nullptr; + return return_clients; } AOClient* Server::getClientByID(int id)