diff --git a/.gitignore b/.gitignore index 2e3d0fbf..c579ae3c 100644 --- a/.gitignore +++ b/.gitignore @@ -415,4 +415,11 @@ Common/x64/ ExternalLibraries/vcpkg/ MainServer/x64/ MainServer/Release/ -Microvolts-Emulator-V2/ \ No newline at end of file +Microvolts-Emulator-V2/ + +ExternalLibraries/CommonLib/Common.lib +E x t e r n a l L i b r a r i e s / C o m m o n . l i b + + E x t e r n a l L i b r a r i e s / C o m m o n . l i b + + \ No newline at end of file diff --git a/AuthServer/AuthServer.vcxproj b/AuthServer/AuthServer.vcxproj deleted file mode 100644 index f01aff20..00000000 --- a/AuthServer/AuthServer.vcxproj +++ /dev/null @@ -1,181 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {9b06c7ac-41f2-459f-b8de-1404c0ddd262} - AuthServer - 10.0 - - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - $(SolutionDir)$(Platform)\ - - - ..\ExternalLibraries\vcpkg\installed - - - ..\ExternalLibraries\vcpkg\vcpkg_installed - - - ..\ExternalLibraries\vcpkg\vcpkg_installed - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - ..\Common\include; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries - stdc17 - - - Console - true - ..\ExternalLibraries\CommonLib\Common.lib - - - - - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - - - Console - true - true - true - - - xcopy "$(SolutionDir)SharedFiles\GameDatabase.db" "$(TargetDir)" - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - ..\Common\include; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries - - - Console - true - ..\ExternalLibraries\CommonLib\Common.lib - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions); _CRT_SECURE_NO_WARNINGS - true - ..\Common\include; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries; - stdcpp20 - - - Console - true - true - true - ..\ExternalLibraries\CommonLib\Common.lib - ..\ExternalLibraries\vcpkg\vcpkg_installed\x64-windows\lib - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/AuthServer/AuthServer.vcxproj.filters b/AuthServer/AuthServer.vcxproj.filters deleted file mode 100644 index 0a92089e..00000000 --- a/AuthServer/AuthServer.vcxproj.filters +++ /dev/null @@ -1,63 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - File di origine - - - File di origine - - - File di origine - - - File di origine - - - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - \ No newline at end of file diff --git a/AuthServer/AuthServerMain.cpp b/AuthServer/AuthServerMain.cpp index 143841ac..bc9fcdfa 100644 --- a/AuthServer/AuthServerMain.cpp +++ b/AuthServer/AuthServerMain.cpp @@ -1,26 +1,46 @@ -#include +#include "Utils/Logger.h" #include #include #include -#include "include/AuthServer.h" +#include "../include/AuthServer.h" + +#include +#include +#include "Utils/Utils.h" -void printInitialInformation() +int main() { + Common::Utils::setConsoleTitle(L"Microvolts Auth Server"); + auto const time = std::chrono::current_zone()->to_local(std::chrono::system_clock::now()); auto const time_s = std::format("{:%Y-%m-%d %X}", time); - std::cout << "Auth server initialized on " << time_s << "\n\n"; -} + Utils::Logger::log("Auth server initialized on " + time_s, Utils::LogType::Info, "AuthServer"); + auto parsedServerInfo = Common::Utils::SetupParser::getInstance().getAuthSetup(); -int main() -{ -#ifdef WIN32 - SetConsoleTitleW(L"Microvolts Auth Server"); -#endif - asio::io_context io_context; - Auth::AuthServer srv(io_context, 13001); + Utils::Logger::log(std::format("Server Information: IP: {}, Port: {}", + parsedServerInfo.ip, parsedServerInfo.port), Utils::LogType::Normal); - printInitialInformation(); + const std::string banner = R"( + _____ __ __ ____ _ ______ _ _ + / ____| \ \ / / | _ \ (_) | ____| | | | | + | (___ __\ \ /\ / /__| |_) | ___ __ _ _ _ __ | |__ _ __ ___ _ _| | __ _| |_ ___ _ __ + \___ \ / _ \ \/ \/ / _ \ _ < / _ \/ _` | | '_ \ | __| | '_ ` _ \| | | | |/ _` | __/ _ \| '__| + ____) | (_) \ /\ / __/ |_) | __/ (_| | | | | | | |____| | | | | | |_| | | (_| | || (_) | | + |_____/ \___/ \/ \/ \___|____/ \___|\__, |_|_| |_| |______|_| |_| |_|\__,_|_|\__,_|\__\___/|_| + __/ | + |___/ + + GitHub: https://github.com/SoWeBegin/MicrovoltsEmulator + +)"; + + Utils::Logger::log(banner, Utils::LogType::Info); + + + + asio::io_context io_context; + Auth::AuthServer srv(io_context, parsedServerInfo.ip, parsedServerInfo.port); srv.asyncAccept(); io_context.run(); -} \ No newline at end of file +} diff --git a/AuthServer/CMakeLists.txt b/AuthServer/CMakeLists.txt new file mode 100644 index 00000000..b53303d8 --- /dev/null +++ b/AuthServer/CMakeLists.txt @@ -0,0 +1,27 @@ +project(AuthServer LANGUAGES CXX) + + +file(GLOB_RECURSE AUTH_SOURCES CONFIGURE_DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/*.cpp +) + +add_executable(AuthServer + ${AUTH_SOURCES} +) + +target_include_directories(AuthServer PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include) + +target_link_libraries(AuthServer PUBLIC + Common + unofficial::mariadb-connector-cpp::mariadbcpp +) + +if (WIN32) + target_link_libraries(AuthServer PUBLIC wsock32 ws2_32) +endif() + + +set_target_properties(AuthServer PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR} +) + diff --git a/AuthServer/include/Structures/AuthAccountInfo.h b/AuthServer/include/Structures/AuthAccountInfo.h index 6ee1c6ca..1e103922 100644 --- a/AuthServer/include/Structures/AuthAccountInfo.h +++ b/AuthServer/include/Structures/AuthAccountInfo.h @@ -2,12 +2,13 @@ #define AUTH_ACCOUNTINFO_H #include +#include "Macros.h" namespace Auth { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct BasicAccountInfo { std::uint32_t accountId{}; @@ -29,7 +30,7 @@ namespace Auth // commandHeader.option = player grade // commandHeader.extra = login type }; -#pragma pack(pop) +PACK_POP() } } diff --git a/AuthServer/include/Structures/AuthChannels.h b/AuthServer/include/Structures/AuthChannels.h index 9917ac64..811d1788 100644 --- a/AuthServer/include/Structures/AuthChannels.h +++ b/AuthServer/include/Structures/AuthChannels.h @@ -8,8 +8,8 @@ namespace Auth { namespace Structures { -#pragma pack(push, 1) - struct ChannelsInfo +PACK_PUSH(1) +struct ChannelsInfo { std::vector channels{}; @@ -22,7 +22,7 @@ namespace Auth } } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/AuthServer/src/DbPlayerInfo.cpp b/AuthServer/src/DbPlayerInfo.cpp index 1ce79d41..3ad0b42c 100644 --- a/AuthServer/src/DbPlayerInfo.cpp +++ b/AuthServer/src/DbPlayerInfo.cpp @@ -141,8 +141,10 @@ namespace Auth playerInfo.setExtra(Auth::Enums::Login::SUCCESS); playerInfoStructure.accountId = static_cast(res->getInt("AccountID")); - strcpy_s(playerInfoStructure.playerName, res->getString("Nickname").c_str()); - strcpy_s(playerInfoStructure.clanName, res->getString("Clanname").c_str()); + std::strncpy(playerInfoStructure.playerName, res->getString("Nickname").c_str(), sizeof(playerInfoStructure.playerName) - 1); + playerInfoStructure.playerName[sizeof(playerInfoStructure.playerName) - 1] = '\0'; + std::strncpy(playerInfoStructure.clanName, res->getString("Clanname").c_str(), sizeof(playerInfoStructure.clanName) - 1); + playerInfoStructure.clanName[sizeof(playerInfoStructure.clanName) - 1] = '\0'; playerInfo.setOption(static_cast(res->getInt("Grade"))); playerInfoStructure.level = static_cast(res->getInt("Level")) + 1; @@ -231,8 +233,14 @@ namespace Auth updateStmt->setString(1, username); updateStmt->executeUpdate(); - if (nonHashedPassword != std::to_string(auth::generateToken(secret))) - { + auto generatedToken = auth::generateToken(secret); + + std::ostringstream tokenStream; + tokenStream << std::setw(6) << std::setfill('0') << generatedToken; + auto tokenString = tokenStream.str(); + + if (nonHashedPassword != tokenString) + { playerInfo.setExtra(Auth::Enums::Login::INCORRECT); return std::pair{ playerInfo, Auth::Structures::BasicAccountInfo{} }; } diff --git a/AuthServer/src/main.cpp b/AuthServer/src/main.cpp deleted file mode 100644 index e49e113a..00000000 --- a/AuthServer/src/main.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "Utils/Logger.h" -#include -#include -#include -#include "../include/AuthServer.h" - -#include -#include - - -int main() -{ - SetConsoleTitleW(L"Microvolts Auth Server"); - - auto const time = std::chrono::current_zone()->to_local(std::chrono::system_clock::now()); - auto const time_s = std::format("{:%Y-%m-%d %X}", time); - Utils::Logger::log("Auth server initialized on " + time_s, Utils::LogType::Info, "AuthServer"); - - auto parsedServerInfo = Common::Utils::SetupParser::getInstance().getAuthSetup(); - Utils::Logger::log(std::format("Server Information: IP: {}, Port: {}", - parsedServerInfo.ip, parsedServerInfo.port), Utils::LogType::Normal); - - asio::io_context io_context; - Auth::AuthServer srv(io_context, parsedServerInfo.ip, parsedServerInfo.port); - srv.asyncAccept(); - io_context.run(); -} \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 00000000..3cf1f5a1 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,34 @@ + +cmake_minimum_required(VERSION 3.26) +project(Microvolts-Emulator-V2 LANGUAGES C CXX ASM) + +set(CMAKE_CXX_STANDARD 23) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(Boost_NO_BOOST_CMAKE ON) +set(Boost_NO_BOOST_VERSION_CHECK ON) + +if (WIN32) + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS}") +else() + set(CMAKE_EXECUTABLE_SUFFIX ".elf") + set(CMAKE_EXE_LINKER_FLAGS + "${CMAKE_EXE_LINKER_FLAGS} -Wl,--allow-multiple-definition -Wl,-rpath=. -Wl,-rpath=/data/data/com.termux/files/usr/lib -Wl,--enable-new-dtags" + ) + + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/Output) + set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/Output) + set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/Output) +endif() + +find_package(Boost REQUIRED COMPONENTS system json beast asio) +find_package(OpenSSL REQUIRED) +find_package(cryptopp CONFIG REQUIRED) +find_package(range-v3 CONFIG REQUIRED) +find_package(unofficial-mariadb-connector-cpp CONFIG REQUIRED) + +include_directories(PUBLIC ${CMAKE_SOURCE_DIR}/ExternalLibraries) +add_subdirectory(Common) +add_subdirectory(AuthServer) +add_subdirectory(CastServer) +add_subdirectory(MainServer) diff --git a/CastServer/CMakeLists.txt b/CastServer/CMakeLists.txt new file mode 100644 index 00000000..63507a84 --- /dev/null +++ b/CastServer/CMakeLists.txt @@ -0,0 +1,33 @@ +project(CastServer LANGUAGES CXX) + + +file(GLOB_RECURSE CAST_SOURCES CONFIGURE_DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/*.cpp +) + +add_executable(CastServer + ${CAST_SOURCES} +) + + +target_include_directories(CastServer PUBLIC + ${CMAKE_CURRENT_LIST_DIR}/include +) + +find_package(directxmath CONFIG REQUIRED) + +target_link_libraries(CastServer + PUBLIC + Common + Boost::system +) + + +if (WIN32) + target_link_libraries(CastServer PUBLIC wsock32 ws2_32) +endif() + +set_target_properties(CastServer PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR} +) + diff --git a/CastServer/CastServer.vcxproj b/CastServer/CastServer.vcxproj deleted file mode 100644 index 12826dac..00000000 --- a/CastServer/CastServer.vcxproj +++ /dev/null @@ -1,191 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {8e0e7ff9-66ac-4f7d-9ee6-a67db4e27b24} - CastServer - 10.0 - - - - Application - true - v143 - Unicode - - - Application - false - ClangCL - true - Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - $(SolutionDir)$(Platform)\ - - - ..\ExternalLibraries\vcpkg\installed - - - ..\ExternalLibraries\vcpkg\vcpkg_installed - - - ..\ExternalLibraries\vcpkg\vcpkg_installed - - - ..\ExternalLibraries\vcpkg\vcpkg_installed - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - ..\Common\include; ..\ExternalLibraries\asio-1.24.0\include; - stdcpp20 - - - Console - true - ..\ExternalLibraries\CommonLib\Common.lib - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpplatest - stdc17 - Speed - ..\Common\include; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries - - - Console - true - true - true - ..\ExternalLibraries\CommonLib\Common.lib - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - ..\Common\include; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries - stdcpplatest - - - Console - true - ..\ExternalLibraries\CommonLib\Common.lib - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions);_CRT_SECURE_NO_WARNINGS - true - ..\Common\include; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries; ..\ExternalLibraries\vcpkg\vcpkg_installed\x64-windows\include - stdcpplatest - MaxSpeed - Speed - AnySuitable - -O2 %(AdditionalOptions) - true - - - Console - false - true - true - ..\ExternalLibraries\CommonLib\Common.lib - ..\ExternalLibraries\vcpkg\vcpkg_installed\x64-windows\lib - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/CastServer/CastServer.vcxproj.filters b/CastServer/CastServer.vcxproj.filters deleted file mode 100644 index e861b74e..00000000 --- a/CastServer/CastServer.vcxproj.filters +++ /dev/null @@ -1,81 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - File di origine - - - File di origine - - - File di origine - - - File di origine - - - File di origine - - - File di origine - - - File di origine - - - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - \ No newline at end of file diff --git a/CastServer/include/CastServer.h b/CastServer/include/CastServer.h index 05c548fc..5ab4010d 100644 --- a/CastServer/include/CastServer.h +++ b/CastServer/include/CastServer.h @@ -24,6 +24,7 @@ namespace Cast std::uint16_t m_serverId; Cast::Network::SessionsManager m_sessionsManager{}; static inline Cast::Classes::RoomsManager m_roomsManager{}; + std::shared_ptr m_positionTimer; tcp::acceptor m_mainServerAcceptor; std::optional m_mainSocket; @@ -34,6 +35,7 @@ namespace Cast CastServer(ioContext& io_context, const std::string& serverIp, std::uint16_t port, std::uint16_t mainPort, std::uint16_t serverId); void asyncAccept(); void asyncAcceptMainServer(); + void tickPositionFlush(); }; } diff --git a/CastServer/include/Classes/Room.h b/CastServer/include/Classes/Room.h index 330a067a..64b29bf2 100644 --- a/CastServer/include/Classes/Room.h +++ b/CastServer/include/Classes/Room.h @@ -27,9 +27,13 @@ namespace Cast std::uint32_t m_roomNumber = -1; std::uint32_t m_serverId{}; + std::vector m_pendingPositions; + public: bool m_hasMatchStarted{}; bool m_isInvisible{}; + std::uint32_t m_roomTick{}; + // Arena Mode bool m_isArenaMode{}; @@ -92,6 +96,8 @@ namespace Cast void broadcastToMatch(Common::Network::UnecryptedPacket& packet); + void broadcastToMatchExceptSelf(Common::Network::UnecryptedPacket& packet, std::uint32_t selfId); + void playerForwardToHost(std::uint64_t hostSessionId, std::uint64_t senderSessionId, Common::Network::UnecryptedPacket& packet); void hostForwardToPlayer(std::uint64_t, std::uint64_t playerId, Common::Network::UnecryptedPacket& packet, bool useHostIdInTcpHeader = true); @@ -122,6 +128,10 @@ namespace Cast void shuffleCoordinates(); void respawnEveryoneArena(); + + void enqueuePosition(Common::Network::UnecryptedPacket&& pkt); + + void flushPendingPositions(); }; } } diff --git a/CastServer/include/Classes/RoomsManager.h b/CastServer/include/Classes/RoomsManager.h index c27575ae..044b2105 100644 --- a/CastServer/include/Classes/RoomsManager.h +++ b/CastServer/include/Classes/RoomsManager.h @@ -14,8 +14,14 @@ namespace Cast { private: std::array, Common::Constants::maxSessionsPerServer + 1> m_playerSessionIdToRoom{}; + std::vector> m_rooms{}; public: + const auto& getAllRooms() const + { + return m_rooms; + } + void addRoom(std::shared_ptr room, std::uint64_t playerId); void switchRoomJoinOrExit(std::shared_ptr session, std::uint64_t hostSessionId = -1); @@ -28,15 +34,19 @@ namespace Cast void broadcastToMatch(std::uint64_t sessionId, Common::Network::UnecryptedPacket& packet); + void broadcastToMatchExceptSelf(std::uint64_t sessionId, Common::Network::UnecryptedPacket& packet); + void playerForwardToHost(std::uint64_t hostSessionId, std::uint64_t senderSessionId, Common::Network::UnecryptedPacket& packet); + void setRoomTick(std::uint64_t hostSessionId, std::uint64_t tick); + void hostForwardToPlayer(std::uint64_t hostSessionId, std::uint64_t receiverSessionId, Common::Network::UnecryptedPacket& packet, bool useHostSessionIdInTcpHeader = true); void setMapFor(std::uint64_t playerId, std::uint32_t map); void setModeFor(std::uint64_t playerId, std::uint32_t mode); - void setRoomNumberFor(std::uint64_t playerId, std::uint32_t roomNum); + bool setRoomNumberFor(std::uint64_t playerId, std::uint32_t roomNum); void endMatch(std::uint64_t hostId); diff --git a/CastServer/include/Handlers/IpcMainHandlers.h b/CastServer/include/Handlers/IpcMainHandlers.h index 33f45372..e70fe3f3 100644 --- a/CastServer/include/Handlers/IpcMainHandlers.h +++ b/CastServer/include/Handlers/IpcMainHandlers.h @@ -5,6 +5,7 @@ #include "Network/Session.h" #include "../Classes/RoomsManager.h" #include "../../../MainServer/include/Structures/ClientData/Structures.h" +#include namespace Cast { @@ -26,16 +27,13 @@ namespace Cast if (!roomOpt) return; auto& room = *roomOpt; - if (auto s = sm.getSession(session->getId())) + if (auto s = sm.getSession(request.getSession())) { if (request.getExtra() == 0) s->m_isInvisible = false; else if (request.getExtra() == 1) s->m_isInvisible = true; } - else - { - if (request.getExtra() == 2) room->m_isInvisible = true; - else if (request.getExtra() == 3) room->m_isInvisible = false; - } + if (request.getExtra() == 2) room->m_isInvisible = true; + else if (request.getExtra() == 3) room->m_isInvisible = false; } inline void handleAssassinMode(const Common::Network::UnecryptedPacket& request, std::shared_ptr session, @@ -54,12 +52,12 @@ namespace Cast session->asyncWrite(request); } - inline void handleRoomNumber(const Common::Network::UnecryptedPacket& request, std::shared_ptr session, - Cast::Classes::RoomsManager& roomsManager) - { - roomsManager.setRoomNumberFor(request.getSession(), request.getExtra()); - session->asyncWrite(request); - } + inline void handleRoomNumber(const Common::Network::UnecryptedPacket& request, std::shared_ptr session, + Cast::Classes::RoomsManager& roomsManager) + { + roomsManager.setRoomNumberFor(request.getSession(), request.getExtra()); + session->asyncWrite(request); + } inline void handleIpReq(const Common::Network::UnecryptedPacket& request, std::shared_ptr session) { @@ -106,4 +104,4 @@ namespace Cast } } -#endif \ No newline at end of file +#endif diff --git a/CastServer/include/Handlers/PlayerPositionHandler.h b/CastServer/include/Handlers/PlayerPositionHandler.h index fd4d6077..f9e0c039 100644 --- a/CastServer/include/Handlers/PlayerPositionHandler.h +++ b/CastServer/include/Handlers/PlayerPositionHandler.h @@ -11,6 +11,7 @@ #include "AntiCheat/Event.h" #include "../Utils/Utilities.h" #include +#include namespace Cast { @@ -26,9 +27,15 @@ namespace Cast if (!roomOpt) return; auto& room = *roomOpt; - // if (room->m_isInvisible || session->m_isInvisible) return; Cast::Structures::ClientPlayerInfoBasic playerPositionFromClient = Cast::Details::parseData(request); if (playerPositionFromClient.isBad()) return; + room->m_roomTick = playerPositionFromClient.matchTick; + + + if (room->m_isInvisible || session->m_isInvisible) + { + playerPositionFromClient.position.positionZ = 0; + } if (room->m_isAssassinMode) { @@ -44,7 +51,7 @@ namespace Cast } else { - acManager.submitEvent(std::make_unique(session, 14, 1000, "Speed hack (Cheat Engine)", 281)); + acManager.submitEvent(std::make_unique(session, 20, 1000, "Speed hack (Cheat Engine)", 281)); } static Common::Network::UnecryptedPacket response{ 1440, 322, 1 }; @@ -52,20 +59,15 @@ namespace Cast const auto fullSize = request.getFullSize(); - if (fullSize == 36) + if (fullSize == 36) { Cast::Structures::ClientPlayerInfoBullet playerPositionBullet{}; std::memcpy(&playerPositionBullet, request.getData(), sizeof(playerPositionBullet)); - if (playerPositionBullet.isBad()) - { - ::Utils::Logger::log("Bad Player Respawn Position", ::Utils::LogType::Warning, "Cast::handlePlayerPosition"); - return; - } + if (playerPositionBullet.isBad()) return; PlayerInfoResponseWithBullets playerInfoResponseWithBullets; playerInfoResponseWithBullets.specificInfo.enableBullet = true; - - playerInfoResponseWithBullets.tick = playerPositionFromClient.matchTick; + playerInfoResponseWithBullets.specificInfo.enableJump = false; playerInfoResponseWithBullets.position = playerPositionFromClient.position; playerInfoResponseWithBullets.direction = playerPositionFromClient.direction; playerInfoResponseWithBullets.specificInfo.animation1 = playerPositionFromClient.animation1; @@ -78,9 +80,8 @@ namespace Cast playerInfoResponseWithBullets.currentWeapon = playerPositionBullet.bulletStruct.bullet4; response.setData(reinterpret_cast(&playerInfoResponseWithBullets), sizeof(playerInfoResponseWithBullets)); - } - else if (fullSize == 40) + else if (fullSize == 40) // bullet+jump+movement+rotation (simplified) { Cast::Structures::ClientPlayerInfoComplete playerPositionComplete{}; std::memcpy(&playerPositionComplete, request.getData(), request.getDataSize()); @@ -92,8 +93,9 @@ namespace Cast PlayerInfoResponseWithBullets playerInfoResponseWithBullets; playerInfoResponseWithBullets.specificInfo.enableBullet = true; + playerInfoResponseWithBullets.specificInfo.enableJump = true; - playerInfoResponseWithBullets.tick = playerPositionFromClient.matchTick; + // playerInfoResponseWithBullets.tick = playerPositionFromClient.matchTick; playerInfoResponseWithBullets.position = playerPositionFromClient.position; playerInfoResponseWithBullets.direction = playerPositionFromClient.direction; playerInfoResponseWithBullets.specificInfo.animation1 = playerPositionFromClient.animation1; @@ -104,9 +106,7 @@ namespace Cast playerInfoResponseWithBullets.specificInfo.sessionId = static_cast(session->getId()); playerInfoResponseWithBullets.bullets = playerPositionBullet.bulletStruct; playerInfoResponseWithBullets.currentWeapon = playerPositionBullet.bulletStruct.bullet4; - PlayerInfoResponseComplete playerInfoResponseComplete{ playerInfoResponseWithBullets }; - playerInfoResponseComplete.playerInfoBasicResponse.specificInfo.enableJump = true; playerInfoResponseComplete.jump = playerPositionComplete.jumpStruct; response.setData(reinterpret_cast(&playerInfoResponseComplete), sizeof(playerInfoResponseComplete)); @@ -114,7 +114,7 @@ namespace Cast else { PlayerInfoBasicResponse playerInfoBasicResponse; - playerInfoBasicResponse.tick = playerPositionFromClient.matchTick; + // playerInfoBasicResponse.tick = playerPositionFromClient.matchTick; playerInfoBasicResponse.position = playerPositionFromClient.position; playerInfoBasicResponse.direction = playerPositionFromClient.direction; playerInfoBasicResponse.currentWeapon = playerPositionFromClient.weapon; @@ -124,7 +124,8 @@ namespace Cast playerInfoBasicResponse.rotation2 = request.getOption(); playerInfoBasicResponse.rotation3 = playerPositionFromClient.rotation; playerInfoBasicResponse.specificInfo.sessionId = static_cast(session->getId()); - + playerInfoBasicResponse.specificInfo.enableJump = false; + playerInfoBasicResponse.specificInfo.enableBullet = false; if (fullSize == 28) { @@ -134,7 +135,6 @@ namespace Cast { playerInfoBasicResponse.specificInfo.enableJump = true; PlayerInfoResponseWithJump playerInfoResponseWithJump{ playerInfoBasicResponse }; - Cast::Structures::ClientPlayerInfoJump playerPositionJump{}; std::memcpy(&playerPositionJump, request.getData(), request.getDataSize()); @@ -150,8 +150,7 @@ namespace Cast } } - // reminder: this is correct, dont use exceptSelf as that causes log errors in SysLog (host receives [322] command not found) - room->broadcastToRoom(response); + room->enqueuePosition(std::move(response)); } } } diff --git a/CastServer/include/Handlers/SimpleHandlers.h b/CastServer/include/Handlers/SimpleHandlers.h index e43ff0da..7dc172f5 100644 --- a/CastServer/include/Handlers/SimpleHandlers.h +++ b/CastServer/include/Handlers/SimpleHandlers.h @@ -12,6 +12,10 @@ #include "../Network/SessionsManager.h" #include "../Structures/Rest.h" #include +#include "AntiCheat/AntiCheat.h" +#include "AntiCheat/Event.h" +#include +#include namespace Cast { @@ -208,7 +212,7 @@ namespace Cast Cast::Network::SessionsManager& sessionsManager, std::uint32_t m_serverId) { Common::Network::UnecryptedPacket response; - response.setTcpHeader(request.getSession()); + response.setTcpHeader(session->getId()); response.setCommand(72, 1, 0, request.getOption()); session->asyncWrite(response); @@ -303,7 +307,8 @@ namespace Cast inline void handlePlayerRespawn(const Common::Network::UnecryptedPacket& request, std::shared_ptr session, Cast::Classes::RoomsManager& roomsManager, - Cast::Network::SessionsManager& sessionsManager) + Cast::Network::SessionsManager& sessionsManager, + Ac::AntiCheatManager& acManager) { auto roomOpt = roomsManager.getRoom(session->getId()); if (!roomOpt) return; @@ -355,6 +360,8 @@ namespace Cast if (auto targetSession = sessionsManager.getSession(playerRespawnPosition.targetUniqueId.session); targetSession) { + if (targetSession->m_team == Common::Enums::TEAM_OBSERVER) return; + targetSession->isDead = (room->isArenaMode() && room->m_hasMatchStarted) ? true : false; targetSession->m_isInMatch = true; session->m_isInMatch = true; @@ -430,6 +437,8 @@ namespace Cast inline void handleItemPickup(const Common::Network::UnecryptedPacket& request, std::shared_ptr session, Cast::Classes::RoomsManager& roomsManager) { + if (session->m_team == Common::Enums::TEAM_OBSERVER || !session->m_isInMatch) return; + if constexpr (PlayerType == Common::Enums::HOST) { roomsManager.broadcastToMatch(session->getId(), const_cast(request)); @@ -446,6 +455,8 @@ namespace Cast inline void handleZombieAbility(const Common::Network::UnecryptedPacket& request, std::shared_ptr session, Cast::Classes::RoomsManager& roomsManager) { + if (session->m_team == Common::Enums::TEAM_OBSERVER || !session->m_isInMatch) return; + if constexpr (PlayerType == Common::Enums::HOST) { roomsManager.broadcastToMatch(session->getId(), const_cast(request)); @@ -460,4 +471,4 @@ namespace Cast } } -#endif \ No newline at end of file +#endif diff --git a/CastServer/include/Handlers/WeaponKillHandlers.h b/CastServer/include/Handlers/WeaponKillHandlers.h index 9e98be24..dbdb3c94 100644 --- a/CastServer/include/Handlers/WeaponKillHandlers.h +++ b/CastServer/include/Handlers/WeaponKillHandlers.h @@ -82,13 +82,30 @@ namespace Cast Cast::Classes::RoomsManager& roomsManager, Cast::Network::SessionsManager& sessionsManager, Ac::AntiCheatManager& acManager) { auto roomOpt = roomsManager.getRoom(session->getId()); - if (!roomOpt) return; + if (!roomOpt) + { + session->asyncWrite(const_cast(request)); + return; + } auto& room = *roomOpt; const auto attackerUid = Cast::Details::parseData(request, 16); const auto targetUid = Cast::Details::parseData(request, 20); const std::uint16_t targetHp = Cast::Details::parseData(request, 24); + if (room->getMode() == Common::Enums::AiBattle || room->getMode() == Common::Enums::BossBattle) + { + roomsManager.broadcastToMatch(session->getId(), const_cast(request)); + return; + } + + auto attackerSession = sessionsManager.getSession(attackerUid.session); + if (attackerSession && + (attackerSession->m_team == Common::Enums::TEAM_OBSERVER || !attackerSession->m_isInMatch)) + { + return; + } + if (auto targetSession = sessionsManager.getSession(targetUid.session)) { if (targetHp) @@ -117,7 +134,7 @@ namespace Cast { Cast::Handlers::sendPlayerStateUpdate(targetSession->getAccountId(), true); } - if (auto attackerSession = sessionsManager.getSession(attackerUid.session)) + if (attackerSession) { acManager.submitEvent(std::make_unique(attackerSession, 4, 1000, "Room Rape (flooding)", 265)); } @@ -127,19 +144,37 @@ namespace Cast } // mg & shotgun + // issue: in boss battle, mg/shotgun work for NPCs, but they don't disappear when killed with mg/shotgun inline void handleSpecialWeaponDamage(const Common::Network::UnecryptedPacket& request, std::shared_ptr session, Cast::Classes::RoomsManager& roomsManager, Cast::Network::SessionsManager& sessionsManager, Ac::AntiCheatManager& acManager) { auto roomOpt = roomsManager.getRoom(session->getId()); - if (!roomOpt) return; + if (!roomOpt) + { + session->asyncWrite(const_cast(request)); + return; + } auto& room = *roomOpt; std::uint16_t targetHp = Cast::Details::parseDataFromEnd(request, 6); auto targetUid = Cast::Details::parseDataFromEnd(request, 8); auto attackerUid = Cast::Details::parseData(request, 16); + if (room->getMode() == Common::Enums::AiBattle || room->getMode() == Common::Enums::BossBattle) + { + roomsManager.broadcastToMatch(session->getId(), const_cast(request)); + return; + } + + auto attackerSession = sessionsManager.getSession(attackerUid.session); + if (attackerSession && + (attackerSession->m_team == Common::Enums::TEAM_OBSERVER || !attackerSession->m_isInMatch)) + { + return; + } + if (auto targetSession = sessionsManager.getSession(targetUid.session)) { if (targetHp) @@ -167,7 +202,7 @@ namespace Cast { Cast::Handlers::sendPlayerStateUpdate(targetSession->getAccountId(), true); } - if (auto attackerSession = sessionsManager.getSession(attackerUid.session)) + if (attackerSession) { acManager.submitEvent(std::make_unique(attackerSession, 4, 1000, "Room Rape (flooding)", 265)); } @@ -181,10 +216,14 @@ namespace Cast Cast::Network::SessionsManager& sessionsManager) { auto roomOpt = roomsManager.getRoom(session->getId()); - if (!roomOpt) return; + if (!roomOpt) + { + session->asyncWrite(const_cast(request)); + return; + } auto& room = *roomOpt; - if (request.getOption() == 0) + if (request.getOption() == 0 || room->getMode() == Common::Enums::AiBattle || room->getMode() == Common::Enums::BossBattle) { roomsManager.broadcastToMatch(session->getId(), const_cast(request)); } diff --git a/CastServer/include/Network/CastSession.h b/CastServer/include/Network/CastSession.h index e6118f37..211339ac 100644 --- a/CastServer/include/Network/CastSession.h +++ b/CastServer/include/Network/CastSession.h @@ -13,21 +13,21 @@ #include "../../../MainServer/include/Structures/AccountInfo/MainAccountInfo.h" #include "Network/Session.h" #include "../Structures/PlayerPositionFromClient.h" +#include "../include/Enums/PlayerEnums.h" namespace Cast { namespace Network { - class Session final : public Common::Network::Session { protected: std::uint32_t m_roomNumber{}; public: + Common::Enums::Team m_team{}; bool isDead{}; bool m_isInMatch{ 0 }; - Common::Enums::Team m_team{}; std::string m_nickname; bool m_isInvisible{}; diff --git a/CastServer/include/Structures/PlayerPositionFromClient.h b/CastServer/include/Structures/PlayerPositionFromClient.h index aa639b59..c8778f60 100644 --- a/CastServer/include/Structures/PlayerPositionFromClient.h +++ b/CastServer/include/Structures/PlayerPositionFromClient.h @@ -2,8 +2,9 @@ #define PLAYER_POSITION_STRUCTURE_H #include -#include "DirectXPackedVector.h" +#include #include "AntiCheat/Event.h" +#include "Macros.h" namespace Cast { @@ -54,7 +55,7 @@ namespace Cast return (sign << 31) | 0x7F800000; // Infinity case } -#pragma pack(push, 1) +PACK_PUSH(1) struct PositionStruct { DirectX::PackedVector::HALF positionX{}; @@ -67,9 +68,9 @@ namespace Cast return isBadPosition(positionX) || isBadPosition(positionY) || isBadPosition(positionZ); } }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct DirectionStruct { DirectX::PackedVector::HALF directionX{}; @@ -82,10 +83,9 @@ namespace Cast return isBadPosition(directionX) || isBadPosition(directionY) || isBadPosition(directionZ); } }; +PACK_POP() -#pragma pack(pop) - -#pragma pack(push, 1) +PACK_PUSH(1) struct BulletsStruct { DirectX::PackedVector::HALF bullet1{}; @@ -100,10 +100,10 @@ namespace Cast return isBadPosition(bullet1) || isBadPosition(bullet2) || isBadPosition(bullet3) || isBadPosition(bullet4); } }; +PACK_POP() -#pragma pack(pop) -#pragma pack(push, 1) +PACK_PUSH(1) struct JumpStruct { DirectX::PackedVector::HALF jump1; @@ -115,11 +115,9 @@ namespace Cast return isBadPosition(jump1) || isBadPosition(jump2); } }; - -#pragma pack(pop) - +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct ClientPlayerInfoBasic { PositionStruct position; @@ -135,11 +133,11 @@ namespace Cast { return position.isBadPos() || direction.isBadDir(); } - }; -#pragma pack(pop) + }; +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct ClientPlayerInfoJump { ClientPlayerInfoBasic playerPositionBasic; @@ -150,10 +148,10 @@ namespace Cast return playerPositionBasic.isBad() || jumpStruct.isBadJump(); } }; +PACK_POP() -#pragma pack(pop) -#pragma pack(push, 1) +PACK_PUSH(1) struct ClientPlayerInfoBullet { ClientPlayerInfoBasic playerPositionBasic; @@ -164,9 +162,10 @@ namespace Cast return playerPositionBasic.isBad() || bulletStruct.isBadBullets(); } }; -#pragma pack(pop) - -#pragma pack(push, 1) +PACK_POP() + + +PACK_PUSH(1) struct ClientPlayerInfoComplete { ClientPlayerInfoBasic playerPositionBasic; @@ -178,19 +177,18 @@ namespace Cast return playerPositionBasic.isBad() || bulletStruct.isBadBullets() || jumpStruct.isBadJump(); } }; -#pragma pack(pop) - +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct SinglePlayerJoinInfo { char u0[16]{}; Main::Structures::UniqueId uid; std::uint32_t unknown{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct SinglePlayerJoinInfoResponse { Main::Structures::UniqueId uid; @@ -198,9 +196,9 @@ namespace Cast std::uint32_t mode : 5 = 0; // client checks whether the mode is zombie for some reason std::uint32_t playerState : 4 = 0; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1 +PACK_PUSH(1) struct PlayerRespawnPosition { std::uint16_t x{}; @@ -218,6 +216,7 @@ namespace Cast return *this; } }; +PACK_POP() } } -#endif \ No newline at end of file +#endif diff --git a/CastServer/include/Structures/PlayerPositionFromServer.h b/CastServer/include/Structures/PlayerPositionFromServer.h index 39352ec3..83f2e53e 100644 --- a/CastServer/include/Structures/PlayerPositionFromServer.h +++ b/CastServer/include/Structures/PlayerPositionFromServer.h @@ -4,13 +4,14 @@ #include #include "PlayerPositionFromClient.h" +#include "Macros.h" namespace Cast { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct SpecificInfo { std::uint32_t sessionId : 14 = 0; @@ -22,12 +23,12 @@ namespace Cast std::uint32_t unknown : 1 = true; std::uint32_t enableJump : 1 = false; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct PlayerInfoBasicResponse { - std::uint32_t tick{}; + // std::uint32_t tick{}; SpecificInfo specificInfo{}; Cast::Structures::PositionStruct position; Cast::Structures::DirectionStruct direction; @@ -36,20 +37,20 @@ namespace Cast std::uint32_t rotation3 : 9 = 0; std::uint32_t currentWeapon : 4 = 0; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct PlayerInfoResponseWithJump { PlayerInfoBasicResponse playerInfoBasicResponse; Cast::Structures::JumpStruct jump{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct PlayerInfoResponseWithBullets { - std::uint32_t tick{}; + // std::uint32_t tick{}; SpecificInfo specificInfo{}; Cast::Structures::PositionStruct position; Cast::Structures::DirectionStruct direction; @@ -59,16 +60,15 @@ namespace Cast std::uint32_t rotation3 : 9 = 0; std::uint32_t currentWeapon : 4 = 0; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct PlayerInfoResponseComplete { PlayerInfoResponseWithBullets playerInfoBasicResponse; Cast::Structures::JumpStruct jump{}; }; -#pragma pack(pop) - +PACK_POP() } } diff --git a/CastServer/include/Structures/Rest.h b/CastServer/include/Structures/Rest.h index 1033802e..1927a5ad 100644 --- a/CastServer/include/Structures/Rest.h +++ b/CastServer/include/Structures/Rest.h @@ -4,29 +4,30 @@ #include #include "../../../MainServer/include/Structures/AccountInfo/MainAccountUniqueId.h" - +#include "Macros.h" namespace Cast { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct SpecialItem // Used to send special items for assassin mode { std::uint32_t number{}; std::uint32_t itemId{}; Main::Structures::UniqueId uid{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct SpecialItemUse { std::uint32_t itemId{}; Main::Structures::UniqueId uid{}; }; -#pragma pack(pop) +PACK_POP() +PACK_PUSH(1) struct RespawnCoord { std::int32_t x; @@ -34,6 +35,7 @@ namespace Cast std::int32_t z; std::int32_t w = 0; }; +PACK_POP() } } diff --git a/CastServer/include/Structures/SuicideStruct.h b/CastServer/include/Structures/SuicideStruct.h index 457fefb5..62e0f5de 100644 --- a/CastServer/include/Structures/SuicideStruct.h +++ b/CastServer/include/Structures/SuicideStruct.h @@ -4,13 +4,13 @@ #include #include "../../../MainServer/include/Structures/AccountInfo/MainAccountUniqueId.h" - +#include "Macros.h" namespace Cast { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct SuicideStructure { std::uint16_t posX; @@ -21,7 +21,7 @@ namespace Cast Main::Structures::UniqueId uniqueId; std::uint32_t newHp = 0; }; -#pragma pack(pop) +PACK_POP() } } diff --git a/CastServer/include/Utils/Utilities.h b/CastServer/include/Utils/Utilities.h index e67c954b..6a03b214 100644 --- a/CastServer/include/Utils/Utilities.h +++ b/CastServer/include/Utils/Utilities.h @@ -4,6 +4,7 @@ #include #include #include +#include namespace Cast { @@ -72,7 +73,7 @@ namespace Cast return parseDataImpl(request, offset, location); } - bool mustBroadcastDeath(std::uint32_t mode) + inline bool mustBroadcastDeath(std::uint32_t mode) { return (mode == Common::Enums::Elimination || mode == Common::Enums::CaptureTheBattery || mode == Common::Enums::BombBattle || mode == Common::Enums::Clan_BombBattle diff --git a/CastServer/src/CastServer.cpp b/CastServer/src/CastServer.cpp index 43f57784..010f65e8 100644 --- a/CastServer/src/CastServer.cpp +++ b/CastServer/src/CastServer.cpp @@ -4,16 +4,33 @@ #include "../include/Handlers/PlayerPositionHandler.h" #include "../include/Handlers/IpcMainHandlers.h" #include "../include/Handlers/WeaponKillHandlers.h" +#include namespace Cast { + void CastServer::tickPositionFlush() + { + for (auto& room : m_roomsManager.getAllRooms()) + { + room->flushPendingPositions(); + } + + m_positionTimer->expires_after(std::chrono::milliseconds(50)); + m_positionTimer->async_wait([this](auto) { tickPositionFlush(); }); + } CastServer::CastServer(ioContext& io_context, const std::string& serverIp, std::uint16_t port, std::uint16_t mainPort, std::uint16_t serverId) : m_io_context{ io_context } , m_acceptor{ io_context, tcp::endpoint(asio::ip::address::from_string(serverIp), port) } , m_serverId{ serverId } - , m_mainServerAcceptor{ io_context, tcp::endpoint(asio::ip::address::from_string(Common::Utils::SetupParser::getInstance().getSelfCastServerInfo().ip), mainPort)} + , m_mainServerAcceptor{ io_context, tcp::endpoint(asio::ip::address::from_string(Common::Utils::SetupParser::getInstance().getSelfCastServerInfo().ip), mainPort) } { + using namespace std::chrono; + + m_sessionsManager.setRoomsManager(&m_roomsManager); + m_positionTimer = std::make_shared(m_io_context); + tickPositionFlush(); + namespace CN = Common::Network; using namespace Cast::Network; @@ -42,14 +59,14 @@ namespace Cast // Player respawn request Common::Network::Session::addCallback(166, [&](const Common::Network::UnecryptedPacket& request, std::shared_ptr session) { - if (request.getDataSize() != 0) return; + if (m_roomsManager.getModeOf(session->getId()) != Common::Enums::BossBattle && request.getDataSize() != 0) return; session->isDead = false; m_roomsManager.playerForwardToHost(request.getSession(), session->getId(), const_cast(request)); }); // Room creation Common::Network::Session::addCallback(277, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { m_roomsManager.addRoom(std::make_shared(session->getId(), session), + std::shared_ptr session) { m_roomsManager.addRoom(std::make_shared(session->getId(), session), session->getId()); }); // Leaving a room @@ -86,9 +103,27 @@ namespace Cast // Unknown Common::Network::Session::addCallback(280, [&](const Common::Network::UnecryptedPacket& request, std::shared_ptr session) { - m_roomsManager.playerForwardToHost(session->getId(), request.getSession(), const_cast(request)); + m_roomsManager.playerForwardToHost(request.getSession(), session->getId(), const_cast(request)); }); + // AI Battle + Common::Network::Session::addCallback(286, [&](const Common::Network::UnecryptedPacket& request, + std::shared_ptr session) { + if (m_roomsManager.getModeOf(session->getId()) == Common::Enums::AiBattle) + { + m_roomsManager.broadcastToMatch(session->getId(), const_cast(request)); + } + }); + + Common::Network::Session::addCallback(285, [&](const Common::Network::UnecryptedPacket& request, + std::shared_ptr session) { + if (m_roomsManager.getModeOf(session->getId()) == Common::Enums::AiBattle) + { + m_roomsManager.broadcastToMatch(session->getId(), const_cast(request)); + } + }); + + Common::Network::Session::addCallback(281, [&](const Common::Network::UnecryptedPacket& request, std::shared_ptr session) { Cast::Handlers::handlePlayerPosition(request, session, m_roomsManager, m_serverId, m_sessionsManager, m_acManager); }); @@ -102,8 +137,8 @@ namespace Cast // Match leave Common::Network::Session::addCallback(256, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { - session->setIsInMatch(false); + std::shared_ptr session) { + session->setIsInMatch(false); auto roomOpt = m_roomsManager.getRoom(session->getId()); if (!roomOpt) return; @@ -118,11 +153,12 @@ namespace Cast std::shared_ptr session) { // Not sure what this is for -- each player in the room sends this to host (through request.getSession()) + m_roomsManager.broadcastToMatch(session->getId(), const_cast(request)); }); // Player sync (needed because otherwise the player: 1. does not get the time left of the match, and 2. they don't respawn at all) Common::Network::Session::addCallback(309, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { m_roomsManager.hostForwardToPlayer(session->getId(), request.getSession(), + std::shared_ptr session) { m_roomsManager.hostForwardToPlayer(session->getId(), request.getSession(), const_cast(request)); }); // without this e.g. the bomb in "bomb battle" doesn't exist seemingly @@ -134,9 +170,13 @@ namespace Cast // Room tick sync request: // When Option==9 in packet order 257: the non host's client sends packet 79 to the server, which dispatches to the host // This packet asks the host to provide the room sync to the non-host - Common::Network::Session::addCallback < CN::PacketType::UNECRYPTED, Session>(79, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { m_roomsManager.playerForwardToHost(request.getSession(), session->getId(), - const_cast(request)); }); + Common::Network::Session::addCallback( + 79, + [&](const Common::Network::UnecryptedPacket& request, std::shared_ptr session) { + m_roomsManager.playerForwardToHost(request.getSession(), session->getId(), const_cast(request)); + } + ); + // In-room info request, apparenly contains "isDeath" data?? Common::Network::Session::addCallback(306, [&](const Common::Network::UnecryptedPacket& request, @@ -151,12 +191,12 @@ namespace Cast // Zombie team broadcast for players that join the match late Common::Network::Session::addCallback(78, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { m_roomsManager.playerForwardToHost(request.getSession(), session->getId(), + std::shared_ptr session) { m_roomsManager.playerForwardToHost(request.getSession(), session->getId(), const_cast(request)); }); // Next round handler, this is covered in main, not sure why client sends it in cast -- but in any case cast sv must do nothing with it // otherwise if we resend this packet (host=>player or player=>host or broadcast) next round never starts - Common::Network::Session::addCallback(259, [&](const Common::Network::UnecryptedPacket& request, + Common::Network::Session::addCallback(259, [&](const Common::Network::UnecryptedPacket& request, std::shared_ptr session) {}); // CTB battery respawn for host, Other weapon reload @@ -165,12 +205,12 @@ namespace Cast // Bomb Battle related -- NON host Common::Network::Session::addCallback(155, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { m_roomsManager.playerForwardToHost(request.getSession(), session->getId(), + std::shared_ptr session) { m_roomsManager.playerForwardToHost(request.getSession(), session->getId(), const_cast(request)); }); // Bomb battle drop bomb Common::Network::Session::addCallback(156, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { m_roomsManager.playerForwardToHost(request.getSession(), session->getId(), + std::shared_ptr session) { m_roomsManager.playerForwardToHost(request.getSession(), session->getId(), const_cast(request)); }); // Bomb battle for host @@ -189,14 +229,19 @@ namespace Cast std::shared_ptr session) { Cast::Handlers::handleMatchInitialLoading(request, session, m_roomsManager, m_serverId); }); Common::Network::Session::addCallback(276, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { Cast::Handlers::handlePlayerRespawn(request, session, m_roomsManager, m_sessionsManager); }); + std::shared_ptr session) { Cast::Handlers::handlePlayerRespawn(request, session, m_roomsManager, m_sessionsManager, + m_acManager); }); // Room tick providing: // After the host client receives packet 78 from the non-host, it provides the non-host with the updated room tick // Without this handler, the player never respawns (not even TAB shows anything) => The player keeps being in a waiting initial state Common::Network::Session::addCallback(408, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { m_roomsManager.hostForwardToPlayer(session->getId(), request.getSession(), - const_cast(request)); }); + std::shared_ptr session) { + m_roomsManager.hostForwardToPlayer(session->getId(), request.getSession(), + const_cast(request)); + } + ); + // Items respawning Common::Network::Session::addCallback(260, [&](const Common::Network::UnecryptedPacket& request, @@ -226,28 +271,64 @@ namespace Cast std::shared_ptr session) { Cast::Handlers::handleZombieAbility(request, session, m_roomsManager); }); // Reminder: do not broadcast the following packets to the whole room, otherwise "next round" elimination bug happens - // Boss battle - main npcs movement/position + // Boss battle - main npcs movement/position, including boss position & small npcs positions Common::Network::Session::addCallback(282, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { m_roomsManager.broadcastToMatch(session->getId(), const_cast(request)); }); + std::shared_ptr session) + { + m_roomsManager.broadcastToMatchExceptSelf(session->getId(), const_cast(request)); + }); + // Boss battle - npcs projectiles Common::Network::Session::addCallback(326, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) { m_roomsManager.hostForwardToPlayer(request.getSession(), session->getId(), - const_cast(request)); }); + std::shared_ptr session) { + if (m_roomsManager.getModeOf(session->getId()) == Common::Enums::BossBattle) + { + m_roomsManager.broadcastToMatch(session->getId(), const_cast(request)); + } + else + { + m_roomsManager.hostForwardToPlayer(request.getSession(), session->getId(), const_cast(request)); + } + }); // Boss battle - npcs respawn Common::Network::Session::addCallback(328, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) {m_roomsManager.hostForwardToPlayer(request.getSession(), session->getId(), - const_cast(request)); }); + std::shared_ptr session) { + if (m_roomsManager.getModeOf(session->getId()) == Common::Enums::BossBattle) + { + m_roomsManager.broadcastToMatch(session->getId(), const_cast(request)); + } + else + { + m_roomsManager.hostForwardToPlayer(request.getSession(), session->getId(), const_cast(request)); + } + }); // Boss battle - npc boss attack -- this is already sent to all clients Common::Network::Session::addCallback(331, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) {m_roomsManager.hostForwardToPlayer(request.getSession(), session->getId(), - const_cast(request)); }); + std::shared_ptr session) { + if (m_roomsManager.getModeOf(session->getId()) == Common::Enums::BossBattle) + { + m_roomsManager.broadcastToMatch(session->getId(), const_cast(request)); + } + else + { + m_roomsManager.hostForwardToPlayer(request.getSession(), session->getId(), const_cast(request)); + } + }); Common::Network::Session::addCallback(304, [&](const Common::Network::UnecryptedPacket& request, - std::shared_ptr session) {m_roomsManager.hostForwardToPlayer(request.getSession(), session->getId(), - const_cast(request)); }); + std::shared_ptr session) { + if (m_roomsManager.getModeOf(session->getId()) == Common::Enums::BossBattle) + { + m_roomsManager.broadcastToMatch(session->getId(), const_cast(request)); + } + else + { + m_roomsManager.hostForwardToPlayer(request.getSession(), session->getId(), const_cast(request)); + } + }); // Shotgun / Mg damage @@ -267,7 +348,7 @@ namespace Cast Cast::Handlers::handleNormalWeaponDamage(request, session, m_roomsManager, m_sessionsManager, m_acManager); }); } - + // Explosives damage Common::Network::Session::addCallback(264, [&](const Common::Network::UnecryptedPacket& request, std::shared_ptr session) { @@ -280,8 +361,6 @@ namespace Cast m_socket.emplace(m_io_context); m_acceptor.async_accept(*m_socket, [&](asio::error_code error) { - m_sessionsManager.setRoomsManager(&m_roomsManager); - auto client = std::make_shared(std::move(*CastServer::m_socket), std::bind(&Cast::Network::SessionsManager::removeSession, &m_sessionsManager, std::placeholders::_1)); client->m_checkValidSession = true; diff --git a/CastServer/src/CastServerMain.cpp b/CastServer/src/CastServerMain.cpp index 9f1f964e..1c83348b 100644 --- a/CastServer/src/CastServerMain.cpp +++ b/CastServer/src/CastServerMain.cpp @@ -25,7 +25,7 @@ void printInitialInformation() int main() { - SetConsoleTitleW(L"Microvolts Cast Server"); + Common::Utils::setConsoleTitle(L"Microvolts Cast Server"); printInitialInformation(); asio::io_context io_context; @@ -36,6 +36,23 @@ int main() parsedServerInfo.ipcPort, parsedServerInfo.serverNumber), Utils::LogType::Normal); + const std::string banner = R"( + + _____ __ __ ____ _ ______ _ _ + / ____| \ \ / / | _ \ (_) | ____| | | | | + | (___ __\ \ /\ / /__| |_) | ___ __ _ _ _ __ | |__ _ __ ___ _ _| | __ _| |_ ___ _ __ + \___ \ / _ \ \/ \/ / _ \ _ < / _ \/ _` | | '_ \ | __| | '_ ` _ \| | | | |/ _` | __/ _ \| '__| + ____) | (_) \ /\ / __/ |_) | __/ (_| | | | | | | |____| | | | | | |_| | | (_| | || (_) | | + |_____/ \___/ \/ \/ \___|____/ \___|\__, |_|_| |_| |______|_| |_| |_|\__,_|_|\__,_|\__\___/|_| + __/ | + |___/ + + GitHub: https://github.com/SoWeBegin/MicrovoltsEmulator + +)"; + + Utils::Logger::log(banner, Utils::LogType::Info); + Cast::CastServer srv(io_context, parsedServerInfo.ip, parsedServerInfo.port, parsedServerInfo.ipcPort, parsedServerInfo.serverNumber); srv.asyncAccept(); diff --git a/CastServer/src/Classes/Room.cpp b/CastServer/src/Classes/Room.cpp index d909a6ff..7f8c7de6 100644 --- a/CastServer/src/Classes/Room.cpp +++ b/CastServer/src/Classes/Room.cpp @@ -8,6 +8,8 @@ #include #include #include +#include +#include namespace Cast { @@ -169,6 +171,22 @@ namespace Cast } } + void Room::broadcastToMatchExceptSelf(Common::Network::UnecryptedPacket& packet, std::uint32_t selfId) + { + for (auto& currentPlayer : m_playersVec) + { + if (auto player = currentPlayer.lock()) + { + if (!player->m_isInMatch) continue; + if (player->getId() == selfId) continue; + + packet.setTcpHeader(player->getId()); + player->asyncWrite(packet); + } + } + } + + bool Room::isInMatch(std::uint64_t playerSessionId) const { auto it = std::find_if(m_playersVec.begin(), m_playersVec.end(), @@ -369,7 +387,41 @@ namespace Cast broadcastToMatch(response); } } + } + void Room::enqueuePosition(Common::Network::UnecryptedPacket&& pkt) + { + m_pendingPositions.push_back(std::move(pkt)); } + + void Room::flushPendingPositions() + { + if (m_pendingPositions.empty()) return; + + static std::array batchBuffer; + std::size_t totalSize = 0; + std::size_t count = 0; + + for (auto& pkt : m_pendingPositions) + { + const auto size = pkt.getDataSize(); + if (totalSize + size > 2036) break; // 8 bytes header + 4 bytes roomtick + std::memcpy(batchBuffer.data() + totalSize, pkt.getData(), size); + totalSize += size; + ++count; + } + + //const std::uint32_t serverTick = m_roomTick - (((Common::Utils::getCurrentTimestampMs() - timeSinceLastRestart) / 10) - m_roomTick); + std::memmove(batchBuffer.data() + sizeof(m_roomTick), batchBuffer.data(), totalSize); + std::memcpy(batchBuffer.data(), &m_roomTick, sizeof(m_roomTick)); + totalSize += sizeof(m_roomTick); + static Common::Network::UnecryptedPacket batch(2048, 322, static_cast(count)); + batch.setOption(static_cast(count)); + batch.setData(batchBuffer.data(), static_cast(totalSize)); + + broadcastToRoom(batch); + m_pendingPositions.clear(); + } + }; } diff --git a/CastServer/src/Classes/RoomsManager.cpp b/CastServer/src/Classes/RoomsManager.cpp index f6422a30..dacb3c01 100644 --- a/CastServer/src/Classes/RoomsManager.cpp +++ b/CastServer/src/Classes/RoomsManager.cpp @@ -13,6 +13,7 @@ namespace Cast if (playerId < m_playerSessionIdToRoom.size()) { m_playerSessionIdToRoom[playerId] = room; + m_rooms.push_back(std::move(room)); } } @@ -82,6 +83,19 @@ namespace Cast playerRoom->broadcastToMatch(packet); } + void RoomsManager::broadcastToMatchExceptSelf(std::uint64_t sessionId, Common::Network::UnecryptedPacket& packet) + { + if (sessionId >= m_playerSessionIdToRoom.size()) + return; + + auto& playerRoom = m_playerSessionIdToRoom[sessionId]; + + if (!playerRoom) + return; + + playerRoom->broadcastToMatchExceptSelf(packet, sessionId); + } + void RoomsManager::broadcastToRoomExceptSelfAndHost(std::uint64_t sessionId, std::uint64_t hostSessionId, Common::Network::UnecryptedPacket& packet) { if (sessionId >= m_playerSessionIdToRoom.size()) @@ -217,6 +231,7 @@ namespace Cast { return room->m_redAssassinPos; } + return std::nullopt; } void RoomsManager::setModeFor(std::uint64_t playerId, std::uint32_t mode) @@ -233,17 +248,18 @@ namespace Cast } - void RoomsManager::setRoomNumberFor(std::uint64_t playerId, std::uint32_t roomNum) + bool RoomsManager::setRoomNumberFor(std::uint64_t playerId, std::uint32_t roomNum) { if (playerId >= m_playerSessionIdToRoom.size()) - return; + return false; auto& room = m_playerSessionIdToRoom[playerId]; if (!room) - return; + return false; room->setRoomNumber(roomNum); + return true; } bool RoomsManager::exists(std::uint64_t playerId) @@ -293,6 +309,8 @@ namespace Cast if (mustRoomBeRemoved) { + m_rooms.erase(std::remove(m_rooms.begin(), m_rooms.end(), roomToRemove), m_rooms.end()); + for (auto& roomSlot : m_playerSessionIdToRoom) { if (roomSlot == roomToRemove) diff --git a/CastServer/src/Network/SessionsManager.cpp b/CastServer/src/Network/SessionsManager.cpp index 8cfae775..64792b04 100644 --- a/CastServer/src/Network/SessionsManager.cpp +++ b/CastServer/src/Network/SessionsManager.cpp @@ -28,10 +28,6 @@ namespace Cast void SessionsManager::removeSession(std::size_t sessionId) { // The player is inside a room - if (m_sessionsBySessionId.contains(sessionId) && m_sessionsBySessionId[sessionId] && m_sessionsBySessionId[sessionId]->getRoomNumber()) - { - m_sessionsBySessionId[sessionId]->setIsInMatch(false); - } if (m_sessionsBySessionId.contains(sessionId)) { m_sessionsBySessionId[sessionId]->setIsInMatch(false); diff --git a/Client/CgdPasswordUpdater.cpp b/Client/CgdPasswordUpdater.cpp index 8247b4c5..3366b0fc 100644 --- a/Client/CgdPasswordUpdater.cpp +++ b/Client/CgdPasswordUpdater.cpp @@ -9,6 +9,7 @@ #include #include #include +#include const std::string GREEN = "\033[32m"; const std::string ORANGE = "\033[33m"; diff --git a/Common/CMakeLists.txt b/Common/CMakeLists.txt new file mode 100644 index 00000000..e5250829 --- /dev/null +++ b/Common/CMakeLists.txt @@ -0,0 +1,47 @@ +project(Common LANGUAGES CXX) + +file(GLOB_RECURSE COMMON_SOURCES CONFIGURE_DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/*.cpp +) + +add_library(Common STATIC + ${COMMON_SOURCES} +) + +set_target_properties(Common PROPERTIES + ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + + LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + LIBRARY_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib + RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${CMAKE_SOURCE_DIR}/ExternalLibraries/CommonLib +) + +target_include_directories(Common + PUBLIC + ${CMAKE_CURRENT_LIST_DIR} + ${CMAKE_CURRENT_LIST_DIR}/include + ${CMAKE_SOURCE_DIR}/ExternalLibraries + ${CMAKE_SOURCE_DIR}/Common/include/Utils +) + +target_link_libraries(Common + PUBLIC + cryptopp::cryptopp + unofficial::mariadb-connector-cpp::mariadbcpp +) + +if (WIN32) + target_link_libraries(Common PUBLIC wsock32 ws2_32) +endif() diff --git a/Common/Common.cpp b/Common/Common.cpp deleted file mode 100644 index e69de29b..00000000 diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj deleted file mode 100644 index 4e0be2bb..00000000 --- a/Common/Common.vcxproj +++ /dev/null @@ -1,226 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 16.0 - Win32Proj - {b17b4c2b-a318-43ab-bd0b-2a0bb9b4470d} - Common - 10.0 - - - - StaticLibrary - true - v143 - Unicode - - - StaticLibrary - false - v143 - true - Unicode - - - StaticLibrary - true - v143 - Unicode - false - - - StaticLibrary - false - ClangCL - false - Unicode - - - - - - - - - - - - - - - - - - - - - ..\ExternalLibraries\CommonLib\ - - - ..\ExternalLibraries\CommonLib\ - - - ..\ExternalLibraries\CommonLib\ - - - ..\ExternalLibraries\vcpkg\vcpkg_installed - - - ..\ExternalLibraries\vcpkg\vcpkg_installed - - - ..\ExternalLibraries\vcpkg\vcpkg_installed - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - ..\Common; ..\ExternalLibraries\visit_struct; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries - stdcpp20 - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpplatest - ..\Common; ..\ExternalLibraries\visit_struct; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries - - - Console - true - true - true - - - - - Level3 - true - NDEBUG;_CONSOLE;ENABLE_DEBUG_MESSAGES;_CRT_SECURE_NO_WARNINGS - true - stdcpplatest - ..\Common; ..\ExternalLibraries\visit_struct; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries - ProgramDatabase - MaxSpeed - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;ENABLE_DEBUG_MESSAGES;_CRT_SECURE_NO_WARNINGS - true - stdcpplatest - ..\Common; ..\ExternalLibraries\visit_struct; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries; ..\ExternalLibraries\vcpkg\vcpkg_installed\x64-windows\include - Speed - MaxSpeed - AnySuitable - false - false - EditAndContinue - EnableFastChecks - stdc11 - - - Console - true - true - true - - - ..\ExternalLibraries\CommonLib\Common.lib - - - ..\ExternalLibraries\vcpkg\vcpkg_installed\x64-windows\lib - - - - - - \ No newline at end of file diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters deleted file mode 100644 index 8aeca79e..00000000 --- a/Common/Common.vcxproj.filters +++ /dev/null @@ -1,162 +0,0 @@ - - - - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - - {93995380-89BD-4b04-88EB-625FBE52EBFB} - h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - - - {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} - rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - - - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - File di intestazione - - - - - File di origine - - - File di origine - - - File di origine - - - File di origine - - - File di origine - - - File di origine - - - File di origine - - - File di origine - - - \ No newline at end of file diff --git a/Common/include/AntiCheat/AntiCheat.h b/Common/include/AntiCheat/AntiCheat.h index 1c7c32d3..ac524644 100644 --- a/Common/include/AntiCheat/AntiCheat.h +++ b/Common/include/AntiCheat/AntiCheat.h @@ -95,7 +95,7 @@ namespace Ac } public: - AntiCheatManager() + AntiCheatManager() { registerChecker(); registerChecker(); @@ -104,6 +104,7 @@ namespace Ac m_thread = std::thread(&AntiCheatManager::worker, this); } + ~AntiCheatManager() { m_isRunning = false; diff --git a/Common/include/AntiCheat/Checks/PacketFloodingCheck.h b/Common/include/AntiCheat/Checks/PacketFloodingCheck.h index 74b8bc95..c5b6134c 100644 --- a/Common/include/AntiCheat/Checks/PacketFloodingCheck.h +++ b/Common/include/AntiCheat/Checks/PacketFloodingCheck.h @@ -31,12 +31,11 @@ namespace Ac public: std::optional processEvent(const PacketFloodingEvent& event) override { - const std::uint64_t serverTime = Common::Utils::getCurrentTimestampMs(); auto& packetRecords = playerData[event.session->getId()][event.packetId]; - packetRecords.push_back({ serverTime, event }); + packetRecords.push_back({ event.eventTime, event }); while (!packetRecords.empty() && - (serverTime - packetRecords.front().serverTime) > event.analysisWindowMs) + (event.eventTime - packetRecords.front().serverTime) > event.analysisWindowMs) { packetRecords.pop_front(); } @@ -52,13 +51,14 @@ namespace Ac "s (max " + std::to_string(event.maxPacketsPerSecond) + ")" }; packetRecords.clear(); - event.session->closeSocket(); + //event.session->closeSocket(); return flag; } return std::nullopt; } + }; } diff --git a/Common/include/AntiCheat/Checks/PacketReplicaCheck.h b/Common/include/AntiCheat/Checks/PacketReplicaCheck.h index 7929604c..f19c3dc7 100644 --- a/Common/include/AntiCheat/Checks/PacketReplicaCheck.h +++ b/Common/include/AntiCheat/Checks/PacketReplicaCheck.h @@ -32,7 +32,6 @@ namespace Ac public: std::optional processEvent(const PacketReplicationEvent& event) override { - const std::uint64_t serverTime = Common::Utils::getCurrentTimestampMs(); const size_t packetHash = calculatePacketHash(event.data); auto& playerPackets = playerData[event.session->getId()]; @@ -40,14 +39,14 @@ namespace Ac { timestamps.erase( std::remove_if(timestamps.begin(), timestamps.end(), [&](std::uint64_t ts) { - return (serverTime - ts) > analysisWindowMs; + return (event.eventTime - ts) > analysisWindowMs; }), timestamps.end() ); } auto& timestamps = playerPackets[packetHash]; - timestamps.push_back(serverTime); + timestamps.push_back(event.eventTime); if (timestamps.size() >= 4) { @@ -59,7 +58,7 @@ namespace Ac floatToString(analysisWindowMs / 1000.0f) + "s window" }; - playerData[event.session->getId()].clear(); + playerData[event.session->getId()].clear(); event.session->closeSocket(); return flag; diff --git a/Common/include/AntiCheat/Event.h b/Common/include/AntiCheat/Event.h index 5b168c2d..be72a350 100644 --- a/Common/include/AntiCheat/Event.h +++ b/Common/include/AntiCheat/Event.h @@ -1,21 +1,24 @@ #ifndef EVENT_AC_H #define EVENT_AC_H -#include "DirectXPackedVector.h" #include #include +#include "../Utils/Utils.h" +#include "../Network/Session.h" namespace Ac { struct ACEvent { virtual ~ACEvent() = default; + enum class Type { PlayerPosition, PlayerKill, PacketFlooding, - PacketReplication + PacketReplication, + ObserverCheck, } type; }; @@ -26,11 +29,13 @@ namespace Ac std::uint64_t analysisWindowMs; std::string floodingType; std::uint32_t packetId; + std::uint64_t eventTime; PacketFloodingEvent(std::shared_ptr session_, std::size_t maxPacketsPerS_, std::uint64_t analysisWindowMs_, const std::string& type_, std::uint32_t packetId_) : session(session_), maxPacketsPerSecond(maxPacketsPerS_), analysisWindowMs(analysisWindowMs_), - floodingType(type_), packetId(packetId_) + floodingType(type_), packetId(packetId_), + eventTime(Common::Utils::getCurrentTimestampMs()) { type = Type::PacketFlooding; } @@ -38,15 +43,33 @@ namespace Ac static constexpr Type typeValue = Type::PacketFlooding; }; + template + struct OutsideMatchActionEvent : ACEvent + { + std::shared_ptr session; + std::string message; + + OutsideMatchActionEvent(std::shared_ptr session_, const std::string& mss) + : session(session_), message(mss) + { + type = Type::ObserverCheck; + } + + static constexpr Type typeValue = Type::ObserverCheck; + }; + + struct PacketReplicationEvent : ACEvent { std::shared_ptr session; std::uint16_t packetId; std::vector data; + std::uint64_t eventTime; - PacketReplicationEvent(std::shared_ptr session_, std::uint16_t packetId_, + PacketReplicationEvent(std::shared_ptr session_, std::uint16_t packetId_, const std::vector& data_) - : session(session_), packetId(packetId_), data(data_) + : session(session_), packetId(packetId_), data(data_), + eventTime(Common::Utils::getCurrentTimestampMs()) { type = Type::PacketReplication; } diff --git a/Common/include/ConstantDatabase/Cdb.h b/Common/include/ConstantDatabase/Cdb.h index 09f4ca66..4e4a9498 100644 --- a/Common/include/ConstantDatabase/Cdb.h +++ b/Common/include/ConstantDatabase/Cdb.h @@ -9,6 +9,7 @@ #include #include #include "../Utils/Logger.h" +#include namespace Common { diff --git a/Common/include/ConstantDatabase/CdbSingleton.h b/Common/include/ConstantDatabase/CdbSingleton.h index 1521a14f..80ec36b9 100644 --- a/Common/include/ConstantDatabase/CdbSingleton.h +++ b/Common/include/ConstantDatabase/CdbSingleton.h @@ -41,7 +41,7 @@ namespace Common return m_itemByType[itemId]; } - static bool itemExistsInShop(std::uint32_t itemId) requires std::same_as + static bool itemExistsInShop(std::uint32_t itemId) requires (std::same_as) { return m_shopItemIds.contains(itemId); } @@ -125,8 +125,8 @@ namespace Common } static void initialize(const std::string& filePath, const std::string& fileName) - requires std::same_as or std::same_as - or std::same_as + requires (std::same_as or std::same_as + or std::same_as) { m_cdb.parse_non_unique_key(filePath, fileName); } @@ -152,6 +152,21 @@ namespace Common } } + static void filterGambleItemsByRareCapsules(const std::vector& rareCapsuleItems) + { + std::unordered_set rareIds; + rareIds.reserve(rareCapsuleItems.size()); + for (const auto& capsule : rareCapsuleItems) + rareIds.insert(capsule.gi_itemid); + + for (auto& [type, itemList] : m_gambleItems) + { + itemList.erase(std::remove_if(itemList.begin(), itemList.end(), + [&](std::uint32_t id) { return !rareIds.contains(id); }), + itemList.end()); + } + } + CdbSingleton(CdbSingleton const&) = delete; void operator=(CdbSingleton const&) = delete; @@ -162,4 +177,4 @@ namespace Common }; } } -#endif \ No newline at end of file +#endif diff --git a/Common/include/ConstantDatabase/Structures/CdbCapsuleInfo.h b/Common/include/ConstantDatabase/Structures/CdbCapsuleInfo.h index abb1488f..909ab2da 100644 --- a/Common/include/ConstantDatabase/Structures/CdbCapsuleInfo.h +++ b/Common/include/ConstantDatabase/Structures/CdbCapsuleInfo.h @@ -2,13 +2,13 @@ #define CGD_CAPSULE_INFO_H #include "visit_struct/visit_struct.hpp" +#include "Macros.h" namespace Common { - namespace ConstantDatabase { -#pragma pack(push, 1) +PACK_PUSH(1) struct CdbCapsuleInfo { std::uint32_t gi_id = static_cast(-1); @@ -26,7 +26,7 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return gi_id; } constexpr bool isValid() const noexcept { return gi_id != static_cast(-1); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/CdbCapsulePackageInfo.h b/Common/include/ConstantDatabase/Structures/CdbCapsulePackageInfo.h index 8983fd15..c6a31d95 100644 --- a/Common/include/ConstantDatabase/Structures/CdbCapsulePackageInfo.h +++ b/Common/include/ConstantDatabase/Structures/CdbCapsulePackageInfo.h @@ -2,12 +2,13 @@ #define CGD_CAPSULE_PACKAGE_INFO_H #include "visit_struct/visit_struct.hpp" +#include "Macros.h" namespace Common { namespace ConstantDatabase { -#pragma pack(push, 1) +PACK_PUSH(1) struct CdbCapsulePackageInfo { int gi_id{}; @@ -22,7 +23,7 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return gi_infoid; } constexpr bool isValid() const noexcept { return gi_infoid != static_cast(-1); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/CdbCollectionInfo.h b/Common/include/ConstantDatabase/Structures/CdbCollectionInfo.h index c1edf70a..462652d4 100644 --- a/Common/include/ConstantDatabase/Structures/CdbCollectionInfo.h +++ b/Common/include/ConstantDatabase/Structures/CdbCollectionInfo.h @@ -2,12 +2,13 @@ #define CGD_COLLECTION_INFO_H #include "visit_struct/visit_struct.hpp" +#include "Macros.h" namespace Common { namespace ConstantDatabase { -#pragma pack(push, 1) +PACK_PUSH(1) struct CdbCollectionInfo { std::uint32_t ci_id = static_cast(-1); @@ -38,7 +39,7 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return ci_id; } constexpr bool isValid() const noexcept { return ci_id != static_cast(-1); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/CdbEffectInfo.h b/Common/include/ConstantDatabase/Structures/CdbEffectInfo.h index b4ac4376..0eb8c800 100644 --- a/Common/include/ConstantDatabase/Structures/CdbEffectInfo.h +++ b/Common/include/ConstantDatabase/Structures/CdbEffectInfo.h @@ -3,12 +3,14 @@ #include "visit_struct/visit_struct.hpp" #include +#include "Macros.h" namespace Common { namespace ConstantDatabase { -#pragma pack(push, 1) + +PACK_PUSH(1) struct CdbEffectInfo { std::uint32_t ei_id; @@ -28,7 +30,7 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return ei_id; } constexpr bool isValid() const noexcept { return ei_id != static_cast(-1); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/CdbGradeInfo.h b/Common/include/ConstantDatabase/Structures/CdbGradeInfo.h index edc6b453..6568d05b 100644 --- a/Common/include/ConstantDatabase/Structures/CdbGradeInfo.h +++ b/Common/include/ConstantDatabase/Structures/CdbGradeInfo.h @@ -4,12 +4,13 @@ #include "visit_struct/visit_struct.hpp" #include +#include "Macros.h" namespace Common { namespace ConstantDatabase { -#pragma pack(push, 1) +PACK_PUSH(1) struct CdbGradeInfo { std::uint32_t gi_grade = static_cast(-1); @@ -23,7 +24,7 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return gi_grade; } constexpr bool isValid() const noexcept { return gi_grade != static_cast(-1); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/CdbItemInfo.h b/Common/include/ConstantDatabase/Structures/CdbItemInfo.h index 9978e3c6..cd111859 100644 --- a/Common/include/ConstantDatabase/Structures/CdbItemInfo.h +++ b/Common/include/ConstantDatabase/Structures/CdbItemInfo.h @@ -3,13 +3,14 @@ #include "visit_struct/visit_struct.hpp" #include +#include "Macros.h" // This is used for MVSurge, refer to "CdbItemsInfo" for CMV namespace Common { namespace ConstantDatabase { -#pragma pack(push, 1) +PACK_PUSH(1) struct CdbItemInfo { std::uint32_t ii_id = static_cast(-1); @@ -86,7 +87,7 @@ namespace Common constexpr bool isValid() const noexcept { return ii_id != static_cast(-1); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/CdbItemWeapon.h b/Common/include/ConstantDatabase/Structures/CdbItemWeapon.h index df52a5ef..51ae9928 100644 --- a/Common/include/ConstantDatabase/Structures/CdbItemWeapon.h +++ b/Common/include/ConstantDatabase/Structures/CdbItemWeapon.h @@ -4,13 +4,14 @@ #include "visit_struct/visit_struct.hpp" #include "CdbWeaponsInfo.h" #include "CdbItemInfo.h" +#include "Macros.h" namespace Common { namespace ConstantDatabase { // Common stuff between CdbItem and CdbWeapons to reduce map lookups in Cdb.h and CdbUtils.h -#pragma pack(push, 1) +PACK_PUSH(1) struct CdbItemWeapon { std::uint32_t ii_id = static_cast(-1); @@ -54,7 +55,7 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return ii_id; } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/CdbMapInfo.h b/Common/include/ConstantDatabase/Structures/CdbMapInfo.h index 7a5f3a35..1d89b0e7 100644 --- a/Common/include/ConstantDatabase/Structures/CdbMapInfo.h +++ b/Common/include/ConstantDatabase/Structures/CdbMapInfo.h @@ -3,12 +3,14 @@ #include "visit_struct/visit_struct.hpp" #include +#include "Macros.h" namespace Common { namespace ConstantDatabase { -#pragma pack(push, 1) +PACK_PUSH(1) + struct CdbMapInfo { std::uint32_t mi_id = static_cast(-1); @@ -58,7 +60,7 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return mi_id; } constexpr bool isValid() const noexcept { return mi_id != static_cast(-1); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/CdbMissionEventInfo.h b/Common/include/ConstantDatabase/Structures/CdbMissionEventInfo.h index 85892a0a..d40f0120 100644 --- a/Common/include/ConstantDatabase/Structures/CdbMissionEventInfo.h +++ b/Common/include/ConstantDatabase/Structures/CdbMissionEventInfo.h @@ -2,12 +2,13 @@ #define CGD_EVENTMISSIONINFO_H #include "visit_struct/visit_struct.hpp" +#include "Macros.h" namespace Common { namespace ConstantDatabase { -#pragma pack(push, 1) +PACK_PUSH(1) struct CdbEventMissionInfo { std::uint32_t em_id = static_cast(-1); @@ -20,7 +21,7 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return em_id; } constexpr bool isValid() const noexcept { return em_id != static_cast(-1); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/CdbPackageInfos.h b/Common/include/ConstantDatabase/Structures/CdbPackageInfos.h index 447f691d..ee5eb02c 100644 --- a/Common/include/ConstantDatabase/Structures/CdbPackageInfos.h +++ b/Common/include/ConstantDatabase/Structures/CdbPackageInfos.h @@ -2,12 +2,13 @@ #define CDB_PACKAGE_INFOS_H #include "visit_struct/visit_struct.hpp" +#include "Macros.h" namespace Common { namespace ConstantDatabase { -#pragma pack(push, 1) +PACK_PUSH(1) struct CdbItemPackageInfo { std::uint32_t ip_id; @@ -21,10 +22,9 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return ip_infoid; } // this is what the client sends when buying a weapon/item package constexpr bool isValid() const noexcept { return ip_infoid != static_cast(-1); } }; -#pragma pack(pop) +PACK_POP() - -#pragma pack(push, 1) +PACK_PUSH(1) struct CdbWeaponPackageInfo { std::uint32_t pi_id; @@ -39,7 +39,7 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return pi_itemid; } // this is what the client sends when buying a weapon/item package constexpr bool isValid() const noexcept { return pi_itemid != static_cast(-1); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/CdbRewardInfo.h b/Common/include/ConstantDatabase/Structures/CdbRewardInfo.h index 5c881db8..447d6057 100644 --- a/Common/include/ConstantDatabase/Structures/CdbRewardInfo.h +++ b/Common/include/ConstantDatabase/Structures/CdbRewardInfo.h @@ -3,12 +3,13 @@ #include #include "visit_struct/visit_struct.hpp" +#include "Macros.h" namespace Common { namespace ConstantDatabase { -#pragma pack(push,1) +PACK_PUSH(1) struct CdbRewardInfo { std::uint32_t ri_mod = static_cast(-1); @@ -47,8 +48,7 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return ri_mod; } constexpr bool isValid() const noexcept { return ri_mod != static_cast(-1); } }; -#pragma pack(pop) - +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/CdbUpgradeInfo.h b/Common/include/ConstantDatabase/Structures/CdbUpgradeInfo.h index 2d3403f6..c77a5db7 100644 --- a/Common/include/ConstantDatabase/Structures/CdbUpgradeInfo.h +++ b/Common/include/ConstantDatabase/Structures/CdbUpgradeInfo.h @@ -3,12 +3,13 @@ #include "visit_struct/visit_struct.hpp" #include +#include "Macros.h" namespace Common { namespace ConstantDatabase { -#pragma pack(push, 1) +PACK_PUSH(1) struct CdbUpgradeInfo { std::uint32_t ui_id; @@ -29,7 +30,7 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return ui_itemid; } constexpr bool isValid() const noexcept { return ui_itemid != static_cast(-1); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/CdbVendor.h b/Common/include/ConstantDatabase/Structures/CdbVendor.h index 588ef4ab..a039ba49 100644 --- a/Common/include/ConstantDatabase/Structures/CdbVendor.h +++ b/Common/include/ConstantDatabase/Structures/CdbVendor.h @@ -3,12 +3,13 @@ #include "visit_struct/visit_struct.hpp" #include +#include "Macros.h" namespace Common { namespace ConstantDatabase { -#pragma pack(push,1) +PACK_PUSH(1) struct CdbVendorInfo { std::uint32_t vi_id = static_cast(-1); @@ -44,8 +45,8 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return vi_id; } constexpr bool isValid() const noexcept { return vi_id != static_cast(-1); } }; -#pragma pack(pop) - } +PACK_POP() + } } VISITABLE_STRUCT(Common::ConstantDatabase::CdbVendorInfo, diff --git a/Common/include/ConstantDatabase/Structures/CdbWeaponsInfo.h b/Common/include/ConstantDatabase/Structures/CdbWeaponsInfo.h index 4150134b..1a95d4d1 100644 --- a/Common/include/ConstantDatabase/Structures/CdbWeaponsInfo.h +++ b/Common/include/ConstantDatabase/Structures/CdbWeaponsInfo.h @@ -3,12 +3,13 @@ #include "visit_struct/visit_struct.hpp" #include +#include "Macros.h" namespace Common { namespace ConstantDatabase { -#pragma pack(push, 1) +PACK_PUSH(1) struct CdbWeaponInfo { std::uint32_t ii_id = static_cast(-1); @@ -84,8 +85,7 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return ii_id; } constexpr bool isValid() const noexcept { return ii_id != static_cast(-1); } }; - -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/ConstantDatabase/Structures/SetItemInfo.h b/Common/include/ConstantDatabase/Structures/SetItemInfo.h index 0e72664f..aa3eb2da 100644 --- a/Common/include/ConstantDatabase/Structures/SetItemInfo.h +++ b/Common/include/ConstantDatabase/Structures/SetItemInfo.h @@ -2,13 +2,14 @@ #define CGD_SET_ITEM_INFO_H #include "visit_struct/visit_struct.hpp" - +#include "Macros.h" +#include namespace Common { namespace ConstantDatabase { -#pragma pack(push,1) +PACK_PUSH(1) struct SetItemInfo { std::uint32_t si_id = static_cast(-1); @@ -26,11 +27,11 @@ namespace Common constexpr std::uint32_t getId() const noexcept { return si_id; } constexpr bool isValid() const noexcept { return si_id != static_cast(-1); } }; -#pragma pack(pop) +PACK_POP() } } VISITABLE_STRUCT(Common::ConstantDatabase::SetItemInfo, si_id, si_hair, si_face, si_top, si_under, si_pants, si_arms, si_boots, si_acce_A, si_acce_B, si_acce_C); -#endif \ No newline at end of file +#endif diff --git a/Common/include/Enums/GameEnums.h b/Common/include/Enums/GameEnums.h index 1c4d81fb..81e70aaa 100644 --- a/Common/include/Enums/GameEnums.h +++ b/Common/include/Enums/GameEnums.h @@ -1,4 +1,4 @@ - #ifndef COMMON_GAME_ENUMS_H +#ifndef COMMON_GAME_ENUMS_H #define COMMON_GAME_ENUMS_H #include @@ -50,11 +50,15 @@ namespace Common MG = 14, BAZOOKA = 15, GRENADE = 16, - SET = 17, MAX_ITEMTYPE = 18 }; + inline bool isWeapon(ItemType itemType) + { + return itemType >= ItemType::MELEE && itemType <= ItemType::GRENADE; + } + enum PlayerState : std::uint32_t { STATE_EXIT = 0, @@ -125,4 +129,4 @@ namespace Common } } -#endif \ No newline at end of file +#endif diff --git a/Common/include/Network/Packet.h b/Common/include/Network/Packet.h index 212d2d06..791cdb3b 100644 --- a/Common/include/Network/Packet.h +++ b/Common/include/Network/Packet.h @@ -11,7 +11,7 @@ #include "../../include/Network/Packet.h" #include "../../include/Enums/MiscellaneousEnums.h" #include "../../include/Utils/Parser.h" - +#include #include diff --git a/Common/include/Network/Session.h b/Common/include/Network/Session.h index 2c2466a2..606d3441 100644 --- a/Common/include/Network/Session.h +++ b/Common/include/Network/Session.h @@ -16,6 +16,7 @@ #include "Packet.h" #include "SessionIdManager.h" #include +#include "../Enums/GameEnums.h" #ifdef ENABLE_BENCHMARKING #include diff --git a/Common/include/Protocol/CommandHeader.h b/Common/include/Protocol/CommandHeader.h index 3242cee6..55c5c015 100644 --- a/Common/include/Protocol/CommandHeader.h +++ b/Common/include/Protocol/CommandHeader.h @@ -3,12 +3,13 @@ #include #include +#include "Macros.h" namespace Common { namespace Protocol { -#pragma pack(push, 1) +PACK_PUSH(1) class CommandHeader { private: @@ -50,7 +51,7 @@ namespace Common std::uint32_t getOption() const; }; -#pragma pack(pop) +PACK_POP() } } diff --git a/Common/include/Protocol/TcpHeader.h b/Common/include/Protocol/TcpHeader.h index f70282f3..865850d5 100644 --- a/Common/include/Protocol/TcpHeader.h +++ b/Common/include/Protocol/TcpHeader.h @@ -3,12 +3,13 @@ #include #include +#include "Macros.h" namespace Common { namespace Protocol { -#pragma pack(push, 1) +PACK_PUSH(1) class TcpHeader { private: @@ -42,8 +43,7 @@ namespace Common std::uint32_t getCrypt() const; }; -#pragma pack(pop) - +PACK_POP() } } diff --git a/Common/include/Utils/Constants.h b/Common/include/Utils/Constants.h index 411f0261..0655778b 100644 --- a/Common/include/Utils/Constants.h +++ b/Common/include/Utils/Constants.h @@ -43,9 +43,9 @@ namespace Common constexpr inline std::uint16_t maxClanRooms = 30; constexpr inline std::uint16_t maxPartiesPerClan = 4; constexpr inline std::uint32_t clanRoomNumberStart = 151; - constexpr inline std::uint16_t matchBaseExp = 120; + constexpr inline std::uint16_t matchBaseExp = 80; constexpr inline std::uint16_t matchBaseMp = 150; - constexpr inline std::uint16_t maxExpAndMpPerMatch = 1500; + constexpr inline std::uint16_t maxExpAndMpPerMatch = 2000; constexpr inline std::uint16_t clanBaseContribution = 100; constexpr inline std::uint32_t goldLevelBox = 5336571; constexpr inline std::uint32_t silverLevelBox = 5336570; diff --git a/Common/include/Utils/Macros.h b/Common/include/Utils/Macros.h new file mode 100644 index 00000000..762fc855 --- /dev/null +++ b/Common/include/Utils/Macros.h @@ -0,0 +1,18 @@ +#ifndef MACROS_UTILS_CUSTOM_H +#define MACROS_UTILS_CUSTOM_H + + +#if defined(_MSC_VER) +#define PACK_PUSH(n) __pragma(pack(push, n)) +#define PACK_POP() __pragma(pack(pop)) +#elif defined(__GNUC__) || defined(__clang__) +#define STRINGIFY(x) #x +#define PACK_PUSH(n) _Pragma(STRINGIFY(pack(push, n))) +#define PACK_POP() _Pragma("pack(pop)") +#else +#define PACK_PUSH(n) +#define PACK_POP() +#endif + + +#endif \ No newline at end of file diff --git a/Common/include/Utils/Utils.h b/Common/include/Utils/Utils.h index 2ec0eeac..ce2b5a9f 100644 --- a/Common/include/Utils/Utils.h +++ b/Common/include/Utils/Utils.h @@ -22,6 +22,13 @@ #define SEND_DEBUG_MESSAGE(msg, session) (void)0 #endif +#ifdef _WIN32 +#include +#else +#include +#endif + + namespace Common { namespace Utils @@ -101,7 +108,18 @@ namespace Common duration_cast(system_clock::now().time_since_epoch()).count() ); } + + + inline void setConsoleTitle(const std::wstring& title) + { + #ifdef _WIN32 + SetConsoleTitleW(title.c_str()); + #else + std::string utf8title(title.begin(), title.end()); + std::cout << "\033]0;" << utf8title << "\007"; + #endif + } } } -#endif \ No newline at end of file +#endif diff --git a/Common/src/Network/Session.cpp b/Common/src/Network/Session.cpp index c2e38e8a..90e47307 100644 --- a/Common/src/Network/Session.cpp +++ b/Common/src/Network/Session.cpp @@ -4,7 +4,9 @@ #include #include +#ifdef _WIN32 #include +#endif #include "../../include/Network/Session.h" #include "../../include/Utils/Parser.h" @@ -22,7 +24,7 @@ #include #include #include - +#include namespace Common @@ -104,7 +106,7 @@ namespace Common } else { - memcpy_s(&header, headerSize, m_reader.data(), headerSize); + std::memcpy(&header, m_reader.data(), headerSize); } if (header.getSize() >= 1450) @@ -204,7 +206,7 @@ namespace Common struct AuthAck { std::int32_t key{ static_cast(rand() + 1) }; - __time32_t timestamp{ static_cast<__time32_t>(std::time(0)) }; + std::uint32_t timestamp32 = static_cast(std::time(nullptr)); } authAck; m_crypt.KeySetup(authAck.key); @@ -273,4 +275,4 @@ namespace Common return m_id; } } -} \ No newline at end of file +} diff --git a/Common/src/Utils/Logger.cpp b/Common/src/Utils/Logger.cpp index 69f41837..874c5d88 100644 --- a/Common/src/Utils/Logger.cpp +++ b/Common/src/Utils/Logger.cpp @@ -1,4 +1,3 @@ - #include #include #include @@ -9,18 +8,23 @@ #include #include #include "../../include/Utils/Logger.h" + +#ifdef _WIN32 #include +#endif namespace Utils { void Logger::enableAnsiEscapeCodes() { +#ifdef _WIN32 HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); if (hOut == INVALID_HANDLE_VALUE) return; DWORD dwMode = 0; if (!GetConsoleMode(hOut, &dwMode)) return; SetConsoleMode(hOut, dwMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING); +#endif } void Logger::printToConsole(const std::string& message, LogType type) @@ -64,7 +68,12 @@ namespace Utils std::time_t now_c = std::chrono::system_clock::to_time_t(now); std::tm tm_time{}; - localtime_s(&tm_time, &now_c); +#ifdef _WIN32 + localtime_s(&tm_time, &now_c); // Windows +#else + localtime_r(&now_c, &tm_time); // Linux/Unix +#endif + std::stringstream ss; ss << std::put_time(&tm_time, "%Y-%m-%d %X"); return ss.str(); @@ -81,3 +90,4 @@ namespace Utils return "Unknown"; } } + diff --git a/Common/src/Utils/Parser.cpp b/Common/src/Utils/Parser.cpp index 32f11f70..894458d5 100644 --- a/Common/src/Utils/Parser.cpp +++ b/Common/src/Utils/Parser.cpp @@ -4,25 +4,47 @@ #include "../../include/Utils/Parser.h" -#include "Windows.h" + +#ifdef _WIN32 +#include +#include +#endif + + #include namespace Common { namespace Parser { - HANDLE hConsole = 0; - std::ofstream logFile("log.txt", std::ios::app); +#ifdef _WIN32 + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + void setConsoleColor(int color) + { + SetConsoleTextAttribute(hConsole, color); + } +#else + void setConsoleColor(int color) { + + switch(color) { + case 2: std::cout << "\033[32m"; break; // green + case 3: std::cout << "\033[36m"; break; // cyan + case 5: std::cout << "\033[35m"; break; // magenta + case 7: std::cout << "\033[0m"; break; // reset + default: std::cout << "\033[0m"; break; + } + } +#endif + std::ofstream logFile("log.txt", std::ios::app); void log(const std::string& message) { - std::cout << message; - - /* - if (logFile.is_open()) - { - logFile << message << std::endl; - }*/ + std::cout << message; + /* + if (logFile.is_open()) { + logFile << message << std::endl; + } + */ } void printTcpHeader(Common::Protocol::TcpHeader header) @@ -85,11 +107,11 @@ namespace Common void parse(std::uint8_t* data, std::size_t len, std::size_t port, const std::string& origin, const std::string& to, std::int32_t cryptKey, bool first) { - SetConsoleTextAttribute(hConsole, 2); + setConsoleColor(2); std::cout << "\n[" << origin << "->" << to << "]"; - SetConsoleTextAttribute(hConsole, 3); + setConsoleColor(3); - SetConsoleTextAttribute(hConsole, 5); + setConsoleColor(5); std::cout << "[Size:" << len << "] \n"; @@ -101,7 +123,7 @@ namespace Common cryptDefault.RC5Decrypt32(data, data, 4); std::uint32_t actualData; memcpy(&actualData, data, sizeof(std::uint32_t)); - SetConsoleTextAttribute(hConsole, 5); + setConsoleColor(5); const Common::Protocol::TcpHeader header(actualData); printTcpHeader(header); @@ -110,7 +132,7 @@ namespace Common const std::size_t actualSize = header.getSize(); cryptDefault.RC5Encrypt32(data, data, 4); - SetConsoleTextAttribute(hConsole, 7); + setConsoleColor(7); for (std::size_t i = 0; i < actualSize; ++i) { printf("%02X ", static_cast(data[i])); @@ -184,7 +206,7 @@ namespace Common memcpy(&actualCommand, data + 4, sizeof(std::uint32_t)); Common::Protocol::CommandHeader commandHeader{ actualCommand }; - if (commandHeader.getOrder() == 281) return; + if (commandHeader.getOrder() != 281 && commandHeader.getOrder() != 282) return; log("\n[" + origin + "->" + to + "]"); log("[CastServer:" + std::to_string(port) + "]"); diff --git a/ExternalLibraries/CommonLib/Common.lib b/ExternalLibraries/CommonLib/Common.lib deleted file mode 100644 index 2d9d482e..00000000 Binary files a/ExternalLibraries/CommonLib/Common.lib and /dev/null differ diff --git a/ExternalLibraries/SQLiteCpp/Assertion.h b/ExternalLibraries/SQLiteCpp/Assertion.h deleted file mode 100644 index 3d404e2d..00000000 --- a/ExternalLibraries/SQLiteCpp/Assertion.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - * @file Assertion.h - * @ingroup SQLiteCpp - * @brief Definition of the SQLITECPP_ASSERT() macro. - * - * Copyright (c) 2012-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ -#pragma once - -#include - -/** - * SQLITECPP_ASSERT SQLITECPP_ASSERT() is used in destructors, where exceptions shall not be thrown - * - * Define SQLITECPP_ENABLE_ASSERT_HANDLER at the project level - * and define a SQLite::assertion_failed() assertion handler - * to tell SQLiteC++ to use it instead of assert() when an assertion fail. -*/ -#ifdef SQLITECPP_ENABLE_ASSERT_HANDLER - -// if an assert handler is provided by user code, use it instead of assert() -namespace SQLite -{ - -// declaration of the assert handler to define in user code -void assertion_failed(const char* apFile, const int apLine, const char* apFunc, - const char* apExpr, const char* apMsg); - -#ifdef _MSC_VER - #define __func__ __FUNCTION__ -#endif -// call the assert handler provided by user code -#define SQLITECPP_ASSERT(expression, message) \ - if (!(expression)) SQLite::assertion_failed(__FILE__, __LINE__, __func__, #expression, message) - -} // namespace SQLite - -#else - -// if no assert handler provided by user code, use standard assert() -// (note: in release mode assert() does nothing) -#define SQLITECPP_ASSERT(expression, message) assert(expression && message) - -#endif diff --git a/ExternalLibraries/SQLiteCpp/Backup.h b/ExternalLibraries/SQLiteCpp/Backup.h deleted file mode 100644 index aa685fb0..00000000 --- a/ExternalLibraries/SQLiteCpp/Backup.h +++ /dev/null @@ -1,131 +0,0 @@ -/** - * @file Backup.h - * @ingroup SQLiteCpp - * @brief Backup is used to backup a database file in a safe and online way. - * - * Copyright (c) 2015 Shibao HONG (shibaohong@outlook.com) - * Copyright (c) 2015-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ -#pragma once - -#include -#include - -#include -#include - -// Forward declaration to avoid inclusion of in a header -struct sqlite3_backup; - -namespace SQLite -{ - -/** - * @brief RAII encapsulation of a SQLite Database Backup process. - * - * A Backup object is used to backup a source database file to a destination database file - * in a safe and online way. - * - * See also the a reference implementation of live backup taken from the official site: - * https://www.sqlite.org/backup.html - */ -class SQLITECPP_API Backup -{ -public: - /** - * @brief Initialize a SQLite Backup object. - * - * Initialize a SQLite Backup object for the source database and destination database. - * The database name is "main" for the main database, "temp" for the temporary database, - * or the name specified after the AS keyword in an ATTACH statement for an attached database. - * - * Exception is thrown in case of error, then the Backup object is NOT constructed. - * - * @param[in] aDestDatabase Destination database connection - * @param[in] apDestDatabaseName Destination database name - * @param[in] aSrcDatabase Source database connection - * @param[in] apSrcDatabaseName Source database name - * - * @throw SQLite::Exception in case of error - */ - Backup(Database& aDestDatabase, - const char* apDestDatabaseName, - Database& aSrcDatabase, - const char* apSrcDatabaseName); - - /** - * @brief Initialize a SQLite Backup object. - * - * Initialize a SQLite Backup object for source database and destination database. - * The database name is "main" for the main database, "temp" for the temporary database, - * or the name specified after the AS keyword in an ATTACH statement for an attached database. - * - * Exception is thrown in case of error, then the Backup object is NOT constructed. - * - * @param[in] aDestDatabase Destination database connection - * @param[in] aDestDatabaseName Destination database name - * @param[in] aSrcDatabase Source database connection - * @param[in] aSrcDatabaseName Source database name - * - * @throw SQLite::Exception in case of error - */ - Backup(Database& aDestDatabase, - const std::string& aDestDatabaseName, - Database& aSrcDatabase, - const std::string& aSrcDatabaseName); - - /** - * @brief Initialize a SQLite Backup object for main databases. - * - * Initialize a SQLite Backup object for source database and destination database. - * Backup the main databases between the source and the destination. - * - * Exception is thrown in case of error, then the Backup object is NOT constructed. - * - * @param[in] aDestDatabase Destination database connection - * @param[in] aSrcDatabase Source database connection - * - * @throw SQLite::Exception in case of error - */ - Backup(Database& aDestDatabase, - Database& aSrcDatabase); - - // Backup is non-copyable - Backup(const Backup&) = delete; - Backup& operator=(const Backup&) = delete; - - /** - * @brief Execute a step of backup with a given number of source pages to be copied - * - * Exception is thrown when SQLITE_IOERR_XXX, SQLITE_NOMEM, or SQLITE_READONLY is returned - * in sqlite3_backup_step(). These errors are considered fatal, so there is no point - * in retrying the call to executeStep(). - * - * @param[in] aNumPage The number of source pages to be copied, with a negative value meaning all remaining source pages - * - * @return SQLITE_OK/SQLITE_DONE/SQLITE_BUSY/SQLITE_LOCKED - * - * @throw SQLite::Exception in case of error - */ - int executeStep(const int aNumPage = -1); - - /// Return the number of source pages still to be backed up as of the most recent call to executeStep(). - int getRemainingPageCount() const; - - /// Return the total number of pages in the source database as of the most recent call to executeStep(). - int getTotalPageCount() const; - -private: - // Deleter functor to use with smart pointers to close the SQLite database backup in an RAII fashion. - struct Deleter - { - void operator()(sqlite3_backup* apBackup); - }; - - std::unique_ptr mpSQLiteBackup; ///< Pointer to SQLite Database Backup Handle -}; - -} // namespace SQLite diff --git a/ExternalLibraries/SQLiteCpp/Column.h b/ExternalLibraries/SQLiteCpp/Column.h deleted file mode 100644 index 412327ea..00000000 --- a/ExternalLibraries/SQLiteCpp/Column.h +++ /dev/null @@ -1,267 +0,0 @@ -/** - * @file Column.h - * @ingroup SQLiteCpp - * @brief Encapsulation of a Column in a row of the result pointed by the prepared SQLite::Statement. - * - * Copyright (c) 2012-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ -#pragma once - -#include "SQLiteCppExport.h" -#include "Statement.h" -#include "Exception.h" - -#include -#include - -// Forward declarations to avoid inclusion of in a header -struct sqlite3_stmt; - -namespace SQLite -{ - -SQLITECPP_API extern const int INTEGER; ///< SQLITE_INTEGER -SQLITECPP_API extern const int FLOAT; ///< SQLITE_FLOAT -SQLITECPP_API extern const int TEXT; ///< SQLITE_TEXT -SQLITECPP_API extern const int BLOB; ///< SQLITE_BLOB -SQLITECPP_API extern const int Null; ///< SQLITE_NULL - -/** - * @brief Encapsulation of a Column in a row of the result pointed by the prepared Statement. - * - * A Column is a particular field of SQLite data in the current row of result - * of the Statement : it points to a single cell. - * - * Its value can be expressed as a text, and, when applicable, as a numeric - * (integer or floating point) or a binary blob. - * - * Thread-safety: a Column object shall not be shared by multiple threads, because : - * 1) in the SQLite "Thread Safe" mode, "SQLite can be safely used by multiple threads - * provided that no single database connection is used simultaneously in two or more threads." - * 2) the SQLite "Serialized" mode is not supported by SQLiteC++, - * because of the way it shares the underling SQLite precompiled statement - * in a custom shared pointer (See the inner class "Statement::Ptr"). - */ -class SQLITECPP_API Column -{ -public: - /** - * @brief Encapsulation of a Column in a Row of the result. - * - * @param[in] aStmtPtr Shared pointer to the prepared SQLite Statement Object. - * @param[in] aIndex Index of the column in the row of result, starting at 0 - */ - explicit Column(const Statement::TStatementPtr& aStmtPtr, int aIndex); - - /** - * @brief Return a pointer to the named assigned to this result column (potentially aliased) - * - * @see getOriginName() to get original column name (not aliased) - */ - const char* getName() const noexcept; - -#ifdef SQLITE_ENABLE_COLUMN_METADATA - /** - * @brief Return a pointer to the table column name that is the origin of this result column - * - * Require definition of the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro : - * - when building the SQLite library itself (which is the case for the Debian libsqlite3 binary for instance), - * - and also when compiling this wrapper. - */ - const char* getOriginName() const noexcept; -#endif - - /// Return the integer value of the column. - int32_t getInt() const noexcept; - /// Return the 32bits unsigned integer value of the column (note that SQLite3 does not support unsigned 64bits). - uint32_t getUInt() const noexcept; - /// Return the 64bits integer value of the column (note that SQLite3 does not support unsigned 64bits). - int64_t getInt64() const noexcept; - /// Return the double (64bits float) value of the column - double getDouble() const noexcept; - /** - * @brief Return a pointer to the text value (NULL terminated string) of the column. - * - * @warning The value pointed at is only valid while the statement is valid (ie. not finalized), - * thus you must copy it before using it beyond its scope (to a std::string for instance). - */ - const char* getText(const char* apDefaultValue = "") const noexcept; - /** - * @brief Return a pointer to the binary blob value of the column. - * - * @warning The value pointed at is only valid while the statement is valid (ie. not finalized), - * thus you must copy it before using it beyond its scope (to a std::string for instance). - */ - const void* getBlob() const noexcept; - /** - * @brief Return a std::string for a TEXT or BLOB column. - * - * Note this correctly handles strings that contain null bytes. - */ - std::string getString() const; - - /** - * @brief Return the type of the value of the column using sqlite3_column_type() - * - * Return either SQLite::INTEGER, SQLite::FLOAT, SQLite::TEXT, SQLite::BLOB, or SQLite::Null. - * This type may change from one row to the next, since - * SQLite stores data types dynamically for each value and not per column. - * Use Statement::getColumnDeclaredType() to retrieve the declared column type from a SELECT statement. - * - * @warning After a type conversion (by a call to a getXxx on a Column of a Yyy type), - * the value returned by sqlite3_column_type() is undefined. - */ - int getType() const noexcept; - - /// Test if the column is an integer type value (meaningful only before any conversion) - bool isInteger() const noexcept - { - return (SQLite::INTEGER == getType()); - } - /// Test if the column is a floating point type value (meaningful only before any conversion) - bool isFloat() const noexcept - { - return (SQLite::FLOAT == getType()); - } - /// Test if the column is a text type value (meaningful only before any conversion) - bool isText() const noexcept - { - return (SQLite::TEXT == getType()); - } - /// Test if the column is a binary blob type value (meaningful only before any conversion) - bool isBlob() const noexcept - { - return (SQLite::BLOB == getType()); - } - /// Test if the column is NULL (meaningful only before any conversion) - bool isNull() const noexcept - { - return (SQLite::Null == getType()); - } - - /** - * @brief Return the number of bytes used by the text (or blob) value of the column - * - * Return either : - * - size in bytes (not in characters) of the string returned by getText() without the '\0' terminator - * - size in bytes of the string representation of the numerical value (integer or double) - * - size in bytes of the binary blob returned by getBlob() - * - 0 for a NULL value - */ - int getBytes() const noexcept; - - /// Alias returning the number of bytes used by the text (or blob) value of the column - int size() const noexcept - { - return getBytes (); - } - - /// Inline cast operators to basic types - operator char() const - { - return static_cast(getInt()); - } - operator int8_t() const - { - return static_cast(getInt()); - } - operator uint8_t() const - { - return static_cast(getInt()); - } - operator int16_t() const - { - return static_cast(getInt()); - } - operator uint16_t() const - { - return static_cast(getInt()); - } - operator int32_t() const - { - return getInt(); - } - operator uint32_t() const - { - return getUInt(); - } - operator int64_t() const - { - return getInt64(); - } - operator double() const - { - return getDouble(); - } - /** - * @brief Inline cast operator to char* - * - * @see getText - */ - operator const char*() const - { - return getText(); - } - /** - * @brief Inline cast operator to void* - * - * @see getBlob - */ - operator const void*() const - { - return getBlob(); - } - - /** - * @brief Inline cast operator to std::string - * - * Handles BLOB or TEXT, which may contain null bytes within - * - * @see getString - */ - operator std::string() const - { - return getString(); - } - -private: - Statement::TStatementPtr mStmtPtr; ///< Shared Pointer to the prepared SQLite Statement Object - int mIndex; ///< Index of the column in the row of result, starting at 0 -}; - -/** - * @brief Standard std::ostream text inserter - * - * Insert the text value of the Column object, using getText(), into the provided stream. - * - * @param[in] aStream Stream to use - * @param[in] aColumn Column object to insert into the provided stream - * - * @return Reference to the stream used - */ -SQLITECPP_API std::ostream& operator<<(std::ostream& aStream, const Column& aColumn); - -#if __cplusplus >= 201402L || (defined(_MSC_VER) && _MSC_VER >= 1900) // c++14: Visual Studio 2015 - -// Create an instance of T from the first N columns, see declaration in Statement.h for full details -template -T Statement::getColumns() -{ - checkRow(); - checkIndex(N - 1); - return getColumns(std::make_integer_sequence{}); -} - -// Helper function called by getColums -template -T Statement::getColumns(const std::integer_sequence) -{ - return T{Column(mpPreparedStatement, Is)...}; -} - -#endif - -} // namespace SQLite diff --git a/ExternalLibraries/SQLiteCpp/Database.h b/ExternalLibraries/SQLiteCpp/Database.h deleted file mode 100644 index 1ccdc652..00000000 --- a/ExternalLibraries/SQLiteCpp/Database.h +++ /dev/null @@ -1,624 +0,0 @@ -/** - * @file Database.h - * @ingroup SQLiteCpp - * @brief Management of a SQLite Database Connection. - * - * Copyright (c) 2012-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ -#pragma once - -#include "SQLiteCppExport.h" -#include "Column.h" - -// c++17: MinGW GCC version > 8 -// c++17: Visual Studio 2017 version 15.7 -// c++17: macOS unless targetting compatibility with macOS < 10.15 -#ifndef SQLITECPP_HAVE_STD_EXPERIMENTAL_FILESYSTEM -#if __cplusplus >= 201703L - #if defined(__MINGW32__) || defined(__MINGW64__) - #if __GNUC__ > 8 // MinGW requires GCC version > 8 for std::filesystem - #define SQLITECPP_HAVE_STD_FILESYSTEM - #endif - #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED < 101500 - // macOS clang won't let us touch std::filesystem if we're targetting earlier than 10.15 - #elif defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && defined(__IPHONE_13_0) && \ -__IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_13_0 - // build for iOS clang won't let us touch std::filesystem if we're targetting earlier than iOS 13 - #else - #define SQLITECPP_HAVE_STD_FILESYSTEM - #endif -#elif defined(_MSVC_LANG) && _MSVC_LANG >= 201703L - #define SQLITECPP_HAVE_STD_FILESYSTEM -#endif - -// disable the support if the required header is not available -#ifdef __has_include - #if !__has_include() - #undef SQLITECPP_HAVE_STD_FILESYSTEM - #endif - #if !__has_include() - #undef SQLITECPP_HAVE_EXPERIMENTAL_FILESYSTEM - #endif -#endif - -// C++17 allow to disable std::filesystem support -#ifdef SQLITECPP_DISABLE_STD_FILESYSTEM - #undef SQLITECPP_HAVE_STD_FILESYSTEM - #undef SQLITECPP_HAVE_STD_EXPERIMENTAL_FILESYSTEM -#endif - -#ifdef SQLITECPP_HAVE_STD_FILESYSTEM -#include -#endif // c++17 and a suitable compiler - -#else // SQLITECPP_HAVE_STD_EXPERIMENTAL_FILESYSTEM - -#define SQLITECPP_HAVE_STD_FILESYSTEM -#include -namespace std { -namespace filesystem = experimental::filesystem; -} - -#endif // SQLITECPP_HAVE_STD_EXPERIMENTAL_FILESYSTEM - -#include -#include - -// Forward declarations to avoid inclusion of in a header -struct sqlite3; -struct sqlite3_context; - -#ifndef SQLITE_USE_LEGACY_STRUCT // Since SQLITE 3.19 (used by default since SQLiteCpp 2.1.0) -typedef struct sqlite3_value sqlite3_value; -#else // Before SQLite 3.19 (legacy struct forward declaration can be activated with CMake SQLITECPP_LEGACY_STRUCT var) -struct Mem; -typedef struct Mem sqlite3_value; -#endif - - -namespace SQLite -{ - -// Those public constants enable most usages of SQLiteCpp without including in the client application. - -/// The database is opened in read-only mode. If the database does not already exist, an error is returned. -SQLITECPP_API extern const int OPEN_READONLY; // SQLITE_OPEN_READONLY -/// The database is opened for reading and writing if possible, or reading only if the file is write protected -/// by the operating system. In either case the database must already exist, otherwise an error is returned. -SQLITECPP_API extern const int OPEN_READWRITE; // SQLITE_OPEN_READWRITE -/// With OPEN_READWRITE: The database is opened for reading and writing, and is created if it does not already exist. -SQLITECPP_API extern const int OPEN_CREATE; // SQLITE_OPEN_CREATE -/// Enable URI filename interpretation, parsed according to RFC 3986 (ex. "file:data.db?mode=ro&cache=private") -SQLITECPP_API extern const int OPEN_URI; // SQLITE_OPEN_URI -/// Open in memory database -SQLITECPP_API extern const int OPEN_MEMORY; // SQLITE_OPEN_MEMORY -/// Open database in multi-thread threading mode -SQLITECPP_API extern const int OPEN_NOMUTEX; // SQLITE_OPEN_NOMUTEX -/// Open database with thread-safety in serialized threading mode -SQLITECPP_API extern const int OPEN_FULLMUTEX; // SQLITE_OPEN_FULLMUTEX -/// Open database with shared cache enabled -SQLITECPP_API extern const int OPEN_SHAREDCACHE; // SQLITE_OPEN_SHAREDCACHE -/// Open database with shared cache disabled -SQLITECPP_API extern const int OPEN_PRIVATECACHE; // SQLITE_OPEN_PRIVATECACHE -/// Database filename is not allowed to be a symbolic link (Note: only since SQlite 3.31.0 from 2020-01-22) -SQLITECPP_API extern const int OPEN_NOFOLLOW; // SQLITE_OPEN_NOFOLLOW - - -SQLITECPP_API extern const int OK; ///< SQLITE_OK (used by check() bellow) - -SQLITECPP_API extern const char* const VERSION; ///< SQLITE_VERSION string from sqlite3.h used at compile time -SQLITECPP_API extern const int VERSION_NUMBER; ///< SQLITE_VERSION_NUMBER from sqlite3.h used at compile time - -/// Return SQLite version string using runtime call to the compiled library -SQLITECPP_API const char* getLibVersion() noexcept; -/// Return SQLite version number using runtime call to the compiled library -SQLITECPP_API int getLibVersionNumber() noexcept; - -// Public structure for representing all fields contained within the SQLite header. -// Official documentation for fields: https://www.sqlite.org/fileformat.html#the_database_header -struct Header { - unsigned char headerStr[16]; - unsigned int pageSizeBytes; - unsigned char fileFormatWriteVersion; - unsigned char fileFormatReadVersion; - unsigned char reservedSpaceBytes; - unsigned char maxEmbeddedPayloadFrac; - unsigned char minEmbeddedPayloadFrac; - unsigned char leafPayloadFrac; - unsigned long fileChangeCounter; - unsigned long databaseSizePages; - unsigned long firstFreelistTrunkPage; - unsigned long totalFreelistPages; - unsigned long schemaCookie; - unsigned long schemaFormatNumber; - unsigned long defaultPageCacheSizeBytes; - unsigned long largestBTreePageNumber; - unsigned long databaseTextEncoding; - unsigned long userVersion; - unsigned long incrementalVaccumMode; - unsigned long applicationId; - unsigned long versionValidFor; - unsigned long sqliteVersion; -}; - -/** - * @brief RAII management of a SQLite Database Connection. - * - * A Database object manage a list of all SQLite Statements associated with the - * underlying SQLite 3 database connection. - * - * Resource Acquisition Is Initialization (RAII) means that the Database Connection - * is opened in the constructor and closed in the destructor, so that there is - * no need to worry about memory management or the validity of the underlying SQLite Connection. - * - * Thread-safety: a Database object shall not be shared by multiple threads, because : - * 1) in the SQLite "Thread Safe" mode, "SQLite can be safely used by multiple threads - * provided that no single database connection is used simultaneously in two or more threads." - * 2) the SQLite "Serialized" mode is not supported by SQLiteC++, - * because of the way it shares the underling SQLite precompiled statement - * in a custom shared pointer (See the inner class "Statement::Ptr"). - */ -class SQLITECPP_API Database -{ - friend class Statement; // Give Statement constructor access to the mSQLitePtr Connection Handle - -public: - /** - * @brief Open the provided database UTF-8 filename. - * - * Uses sqlite3_open_v2() with readonly default flag, which is the opposite behavior - * of the old sqlite3_open() function (READWRITE+CREATE). - * This makes sense if you want to use it on a readonly filesystem - * or to prevent creation of a void file when a required file is missing. - * - * Exception is thrown in case of error, then the Database object is NOT constructed. - * - * @param[in] apFilename UTF-8 path/uri to the database file ("filename" sqlite3 parameter) - * @param[in] aFlags SQLite::OPEN_READONLY/SQLite::OPEN_READWRITE/SQLite::OPEN_CREATE... - * @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout()) - * @param[in] apVfs UTF-8 name of custom VFS to use, or nullptr for sqlite3 default - * - * @throw SQLite::Exception in case of error - */ - Database(const char* apFilename, - const int aFlags = SQLite::OPEN_READONLY, - const int aBusyTimeoutMs = 0, - const char* apVfs = nullptr); - - /** - * @brief Open the provided database UTF-8 filename. - * - * Uses sqlite3_open_v2() with readonly default flag, which is the opposite behavior - * of the old sqlite3_open() function (READWRITE+CREATE). - * This makes sense if you want to use it on a readonly filesystem - * or to prevent creation of a void file when a required file is missing. - * - * Exception is thrown in case of error, then the Database object is NOT constructed. - * - * @param[in] aFilename UTF-8 path/uri to the database file ("filename" sqlite3 parameter) - * @param[in] aFlags SQLite::OPEN_READONLY/SQLite::OPEN_READWRITE/SQLite::OPEN_CREATE... - * @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout()) - * @param[in] aVfs UTF-8 name of custom VFS to use, or empty string for sqlite3 default - * - * @throw SQLite::Exception in case of error - */ - Database(const std::string& aFilename, - const int aFlags = SQLite::OPEN_READONLY, - const int aBusyTimeoutMs = 0, - const std::string& aVfs = "") : - Database(aFilename.c_str(), aFlags, aBusyTimeoutMs, aVfs.empty() ? nullptr : aVfs.c_str()) - { - } - - #ifdef SQLITECPP_HAVE_STD_FILESYSTEM - - /** - * @brief Open the provided database std::filesystem::path. - * - * @note This feature requires std=C++17 - * - * Uses sqlite3_open_v2() with readonly default flag, which is the opposite behavior - * of the old sqlite3_open() function (READWRITE+CREATE). - * This makes sense if you want to use it on a readonly filesystem - * or to prevent creation of a void file when a required file is missing. - * - * Exception is thrown in case of error, then the Database object is NOT constructed. - * - * @param[in] apFilename Path/uri to the database file ("filename" sqlite3 parameter) - * @param[in] aFlags SQLite::OPEN_READONLY/SQLite::OPEN_READWRITE/SQLite::OPEN_CREATE... - * @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY (see setBusyTimeout()) - * @param[in] apVfs UTF-8 name of custom VFS to use, or nullptr for sqlite3 default - * - * @throw SQLite::Exception in case of error - */ - Database(const std::filesystem::path& apFilename, - const int aFlags = SQLite::OPEN_READONLY, - const int aBusyTimeoutMs = 0, - const std::string& aVfs = "") : - Database(reinterpret_cast(apFilename.u8string().c_str()), - aFlags, aBusyTimeoutMs, aVfs.empty() ? nullptr : aVfs.c_str()) - { - } - - #endif // have std::filesystem - - // Database is non-copyable - Database(const Database&) = delete; - Database& operator=(const Database&) = delete; - - // Database is movable - Database(Database&& aDatabase) = default; - Database& operator=(Database&& aDatabase) = default; - - /** - * @brief Close the SQLite database connection. - * - * All SQLite statements must have been finalized before, - * so all Statement objects must have been unregistered. - * - * @warning assert in case of error - */ - ~Database() = default; - - // Deleter functor to use with smart pointers to close the SQLite database connection in an RAII fashion. - struct Deleter - { - SQLITECPP_API void operator()(sqlite3* apSQLite); - }; - - /** - * @brief Set a busy handler that sleeps for a specified amount of time when a table is locked. - * - * This is useful in multithreaded program to handle case where a table is locked for writing by a thread. - * Any other thread cannot access the table and will receive a SQLITE_BUSY error: - * setting a timeout will wait and retry up to the time specified before returning this SQLITE_BUSY error. - * Reading the value of timeout for current connection can be done with SQL query "PRAGMA busy_timeout;". - * Default busy timeout is 0ms. - * - * @param[in] aBusyTimeoutMs Amount of milliseconds to wait before returning SQLITE_BUSY - * - * @throw SQLite::Exception in case of error - */ - void setBusyTimeout(const int aBusyTimeoutMs); - - /** - * @brief Shortcut to execute one or multiple statements without results. Return the number of changes. - * - * This is useful for any kind of statements other than the Data Query Language (DQL) "SELECT" : - * - Data Manipulation Language (DML) statements "INSERT", "UPDATE" and "DELETE" - * - Data Definition Language (DDL) statements "CREATE", "ALTER" and "DROP" - * - Data Control Language (DCL) statements "GRANT", "REVOKE", "COMMIT" and "ROLLBACK" - * - * @see Database::tryExec() to execute, returning the sqlite result code - * @see Statement::exec() to handle precompiled statements (for better performances) without results - * @see Statement::executeStep() to handle "SELECT" queries with results - * - * @param[in] apQueries one or multiple UTF-8 encoded, semicolon-separate SQL statements - * - * @return number of rows modified by the *last* INSERT, UPDATE or DELETE statement (beware of multiple statements) - * @warning undefined for CREATE or DROP table: returns the value of a previous INSERT, UPDATE or DELETE statement. - * - * @throw SQLite::Exception in case of error - */ - int exec(const char* apQueries); - - /** - * @brief Shortcut to execute one or multiple statements without results. - * - * This is useful for any kind of statements other than the Data Query Language (DQL) "SELECT" : - * - Data Manipulation Language (DML) statements "INSERT", "UPDATE" and "DELETE" - * - Data Definition Language (DDL) statements "CREATE", "ALTER" and "DROP" - * - Data Control Language (DCL) statements "GRANT", "REVOKE", "COMMIT" and "ROLLBACK" - * - * @see Database::tryExec() to execute, returning the sqlite result code - * @see Statement::exec() to handle precompiled statements (for better performances) without results - * @see Statement::executeStep() to handle "SELECT" queries with results - * - * @param[in] aQueries one or multiple UTF-8 encoded, semicolon-separate SQL statements - * - * @return number of rows modified by the *last* INSERT, UPDATE or DELETE statement (beware of multiple statements) - * @warning undefined for CREATE or DROP table: returns the value of a previous INSERT, UPDATE or DELETE statement. - * - * @throw SQLite::Exception in case of error - */ - int exec(const std::string& aQueries) - { - return exec(aQueries.c_str()); - } - - /** - * @brief Try to execute one or multiple statements, returning the sqlite result code. - * - * This is useful for any kind of statements other than the Data Query Language (DQL) "SELECT" : - * - Data Manipulation Language (DML) statements "INSERT", "UPDATE" and "DELETE" - * - Data Definition Language (DDL) statements "CREATE", "ALTER" and "DROP" - * - Data Control Language (DCL) statements "GRANT", "REVOKE", "COMMIT" and "ROLLBACK" - * - * @see exec() to execute, returning number of rows modified - * - * @param[in] apQueries one or multiple UTF-8 encoded, semicolon-separate SQL statements - * - * @return the sqlite result code. - */ - int tryExec(const char* apQueries) noexcept; - - /** - * @brief Try to execute one or multiple statements, returning the sqlite result code. - * - * This is useful for any kind of statements other than the Data Query Language (DQL) "SELECT" : - * - Data Manipulation Language (DML) statements "INSERT", "UPDATE" and "DELETE" - * - Data Definition Language (DDL) statements "CREATE", "ALTER" and "DROP" - * - Data Control Language (DCL) statements "GRANT", "REVOKE", "COMMIT" and "ROLLBACK" - * - * @see exec() to execute, returning number of rows modified - * - * @param[in] aQueries one or multiple UTF-8 encoded, semicolon-separate SQL statements - * - * @return the sqlite result code. - */ - int tryExec(const std::string& aQueries) noexcept - { - return tryExec(aQueries.c_str()); - } - - /** - * @brief Shortcut to execute a one step query and fetch the first column of the result. - * - * This is a shortcut to execute a simple statement with a single result. - * This should be used only for non reusable queries (else you should use a Statement with bind()). - * This should be used only for queries with expected results (else an exception is fired). - * - * @warning WARNING: Be very careful with this dangerous method: you have to - * make a COPY OF THE result, else it will be destroy before the next line - * (when the underlying temporary Statement and Column objects are destroyed) - * - * @see also Statement class for handling queries with multiple results - * - * @param[in] apQuery an UTF-8 encoded SQL query - * - * @return a temporary Column object with the first column of result. - * - * @throw SQLite::Exception in case of error - */ - Column execAndGet(const char* apQuery); - - /** - * @brief Shortcut to execute a one step query and fetch the first column of the result. - * - * This is a shortcut to execute a simple statement with a single result. - * This should be used only for non reusable queries (else you should use a Statement with bind()). - * This should be used only for queries with expected results (else an exception is fired). - * - * @warning WARNING: Be very careful with this dangerous method: you have to - * make a COPY OF THE result, else it will be destroy before the next line - * (when the underlying temporary Statement and Column objects are destroyed) - * - * @see also Statement class for handling queries with multiple results - * - * @param[in] aQuery an UTF-8 encoded SQL query - * - * @return a temporary Column object with the first column of result. - * - * @throw SQLite::Exception in case of error - */ - Column execAndGet(const std::string& aQuery) - { - return execAndGet(aQuery.c_str()); - } - - /** - * @brief Shortcut to test if a table exists. - * - * Table names are case sensitive. - * - * @param[in] apTableName an UTF-8 encoded case sensitive Table name - * - * @return true if the table exists. - * - * @throw SQLite::Exception in case of error - */ - bool tableExists(const char* apTableName) const; - - /** - * @brief Shortcut to test if a table exists. - * - * Table names are case sensitive. - * - * @param[in] aTableName an UTF-8 encoded case sensitive Table name - * - * @return true if the table exists. - * - * @throw SQLite::Exception in case of error - */ - bool tableExists(const std::string& aTableName) const - { - return tableExists(aTableName.c_str()); - } - - /** - * @brief Get the rowid of the most recent successful INSERT into the database from the current connection. - * - * Each entry in an SQLite table always has a unique 64-bit signed integer key called the rowid. - * If the table has a column of type INTEGER PRIMARY KEY, then it is an alias for the rowid. - * - * @return Rowid of the most recent successful INSERT into the database, or 0 if there was none. - */ - int64_t getLastInsertRowid() const noexcept; - - /// Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table). - int getChanges() const noexcept; - - /// Get total number of rows modified by all INSERT, UPDATE or DELETE statement since connection (not DROP table). - int getTotalChanges() const noexcept; - - /// Return the numeric result code for the most recent failed API call (if any). - int getErrorCode() const noexcept; - /// Return the extended numeric result code for the most recent failed API call (if any). - int getExtendedErrorCode() const noexcept; - /// Return UTF-8 encoded English language explanation of the most recent failed API call (if any). - const char* getErrorMsg() const noexcept; - - /// Return the filename used to open the database. - const std::string& getFilename() const noexcept - { - return mFilename; - } - - /** - * @brief Return raw pointer to SQLite Database Connection Handle. - * - * This is often needed to mix this wrapper with other libraries or for advance usage not supported by SQLiteCpp. - */ - sqlite3* getHandle() const noexcept - { - return mSQLitePtr.get(); - } - - /** - * @brief Create or redefine a SQL function or aggregate in the sqlite database. - * - * This is the equivalent of the sqlite3_create_function_v2 command. - * @see http://www.sqlite.org/c3ref/create_function.html - * - * @note UTF-8 text encoding assumed. - * - * @param[in] apFuncName Name of the SQL function to be created or redefined - * @param[in] aNbArg Number of arguments in the function - * @param[in] abDeterministic Optimize for deterministic functions (most are). A random number generator is not. - * @param[in] apApp Arbitrary pointer of user data, accessible with sqlite3_user_data(). - * @param[in] apFunc Pointer to a C-function to implement a scalar SQL function (apStep & apFinal nullptr) - * @param[in] apStep Pointer to a C-function to implement an aggregate SQL function (apFunc nullptr) - * @param[in] apFinal Pointer to a C-function to implement an aggregate SQL function (apFunc nullptr) - * @param[in] apDestroy If not nullptr, then it is the destructor for the application data pointer. - * - * @throw SQLite::Exception in case of error - */ - void createFunction(const char* apFuncName, - int aNbArg, - bool abDeterministic, - void* apApp, - void (*apFunc)(sqlite3_context *, int, sqlite3_value **), - void (*apStep)(sqlite3_context *, int, sqlite3_value **) = nullptr, - void (*apFinal)(sqlite3_context *) = nullptr, // NOLINT(readability/casting) - void (*apDestroy)(void *) = nullptr); - - /** - * @brief Load a module into the current sqlite database instance. - * - * This is the equivalent of the sqlite3_load_extension call, but additionally enables - * module loading support prior to loading the requested module. - * - * @see http://www.sqlite.org/c3ref/load_extension.html - * - * @note UTF-8 text encoding assumed. - * - * @param[in] apExtensionName Name of the shared library containing extension - * @param[in] apEntryPointName Name of the entry point (nullptr to let sqlite work it out) - * - * @throw SQLite::Exception in case of error - */ - void loadExtension(const char* apExtensionName, const char* apEntryPointName); - - /** - * @brief Set the key for the current sqlite database instance. - * - * This is the equivalent of the sqlite3_key call and should thus be called - * directly after opening the database. - * Open encrypted database -> call db.key("secret") -> database ready - * - * @param[in] aKey Key to decode/encode the database - * - * @throw SQLite::Exception in case of error - */ - void key(const std::string& aKey) const; - - /** - * @brief Reset the key for the current sqlite database instance. - * - * This is the equivalent of the sqlite3_rekey call and should thus be called - * after the database has been opened with a valid key. To decrypt a - * database, call this method with an empty string. - * Open normal database -> call db.rekey("secret") -> encrypted database, database ready - * Open encrypted database -> call db.key("secret") -> call db.rekey("newsecret") -> change key, database ready - * Open encrypted database -> call db.key("secret") -> call db.rekey("") -> decrypted database, database ready - * - * @param[in] aNewKey New key to encode the database - * - * @throw SQLite::Exception in case of error - */ - void rekey(const std::string& aNewKey) const; - - /** - * @brief Test if a file contains an unencrypted database. - * - * This is a simple test that reads the first bytes of a database file and - * compares them to the standard header for unencrypted databases. If the - * header does not match the standard string, we assume that we have an - * encrypted file. - * - * @param[in] aFilename path/uri to a file - * - * @return true if the database has the standard header. - * - * @throw SQLite::Exception in case of error - */ - static bool isUnencrypted(const std::string& aFilename); - - /** - * @brief Parse SQLite header data from a database file. - * - * This function reads the first 100 bytes of a SQLite database file - * and reconstructs groups of individual bytes into the associated fields - * in a Header object. - * - * @param[in] aFilename path/uri to a file - * - * @return Header object containing file data - * - * @throw SQLite::Exception in case of error - */ - static Header getHeaderInfo(const std::string& aFilename); - - // Parse SQLite header data from a database file. - Header getHeaderInfo() const - { - return getHeaderInfo(mFilename); - } - - /** - * @brief BackupType for the backup() method - */ - enum BackupType { Save, Load }; - - /** - * @brief Load or save the database content. - * - * This function is used to load the contents of a database file on disk - * into the "main" database of open database connection, or to save the current - * contents of the database into a database file on disk. - * - * @throw SQLite::Exception in case of error - */ - void backup(const char* apFilename, BackupType aType); - - /** - * @brief Check if aRet equal SQLITE_OK, else throw a SQLite::Exception with the SQLite error message - */ - void check(const int aRet) const - { - if (SQLite::OK != aRet) - { - throw SQLite::Exception(getHandle(), aRet); - } - } - -private: - // TODO: perhaps switch to having Statement sharing a pointer to the Connexion - std::unique_ptr mSQLitePtr; ///< Pointer to SQLite Database Connection Handle - std::string mFilename; ///< UTF-8 filename used to open the database -}; - -} // namespace SQLite diff --git a/ExternalLibraries/SQLiteCpp/Exception.h b/ExternalLibraries/SQLiteCpp/Exception.h deleted file mode 100644 index b8e47e85..00000000 --- a/ExternalLibraries/SQLiteCpp/Exception.h +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @file Exception.h - * @ingroup SQLiteCpp - * @brief Encapsulation of the error message from SQLite3 on a std::runtime_error. - * - * Copyright (c) 2012-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ -#pragma once - -#include "SQLiteCppExport.h" -#include -#include - -// Forward declaration to avoid inclusion of in a header -struct sqlite3; - -namespace SQLite -{ - -/** - * @brief Encapsulation of the error message from SQLite3, based on std::runtime_error. - */ -class SQLITECPP_API Exception : public std::runtime_error -{ -public: - /** - * @brief Encapsulation of the error message from SQLite3, based on std::runtime_error. - * - * @param[in] aErrorMessage The string message describing the SQLite error - * @param[in] ret Return value from function call that failed. - */ - Exception(const char* aErrorMessage, int ret); - - Exception(const std::string& aErrorMessage, int ret) : - Exception(aErrorMessage.c_str(), ret) - { - } - - /** - * @brief Encapsulation of the error message from SQLite3, based on std::runtime_error. - * - * @param[in] aErrorMessage The string message describing the SQLite error - */ - explicit Exception(const char* aErrorMessage) : - Exception(aErrorMessage, -1) // 0 would be SQLITE_OK, which doesn't make sense - { - } - explicit Exception(const std::string& aErrorMessage) : - Exception(aErrorMessage.c_str(), -1) // 0 would be SQLITE_OK, which doesn't make sense - { - } - - /** - * @brief Encapsulation of the error message from SQLite3, based on std::runtime_error. - * - * @param[in] apSQLite The SQLite object, to obtain detailed error messages from. - */ - explicit Exception(sqlite3* apSQLite); - - /** - * @brief Encapsulation of the error message from SQLite3, based on std::runtime_error. - * - * @param[in] apSQLite The SQLite object, to obtain detailed error messages from. - * @param[in] ret Return value from function call that failed. - */ - Exception(sqlite3* apSQLite, int ret); - - /// Return the result code (if any, otherwise -1). - int getErrorCode() const noexcept - { - return mErrcode; - } - - /// Return the extended numeric result code (if any, otherwise -1). - int getExtendedErrorCode() const noexcept - { - return mExtendedErrcode; - } - - /// Return a string, solely based on the error code - const char* getErrorStr() const noexcept; - -private: - int mErrcode; ///< Error code value - int mExtendedErrcode; ///< Detailed error code if any -}; - -} // namespace SQLite diff --git a/ExternalLibraries/SQLiteCpp/ExecuteMany.h b/ExternalLibraries/SQLiteCpp/ExecuteMany.h deleted file mode 100644 index b5de3a23..00000000 --- a/ExternalLibraries/SQLiteCpp/ExecuteMany.h +++ /dev/null @@ -1,91 +0,0 @@ -/** - * @file ExecuteMany.h - * @ingroup SQLiteCpp - * @brief Convenience function to execute a Statement with multiple Parameter sets - * - * Copyright (c) 2019 Maximilian Bachmann (contact@maxbachmann.de) - * Copyright (c) 2019-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ -#pragma once - -#if (__cplusplus >= 201402L) || ( defined(_MSC_VER) && (_MSC_VER >= 1900) ) // c++14: Visual Studio 2015 - -#include -#include - -/// @cond -#include -#include -#include - -namespace SQLite -{ - -/// @endcond - -/** - * \brief Convenience function to execute a Statement with multiple Parameter sets once for each parameter set given. - * - * - * This feature requires a c++14 capable compiler. - * - * \code{.cpp} - * execute_many(db, "INSERT INTO test VALUES (?, ?)", - * 1, - * std::make_tuple(2), - * std::make_tuple(3, "three") - * ); - * \endcode - * @param aDatabase Database to use - * @param apQuery Query to use with all parameter sets - * @param aArg first tuple with parameters - * @param aParams the following tuples with parameters - */ -template -void execute_many(Database& aDatabase, const char* apQuery, Arg&& aArg, Types&&... aParams) -{ - SQLite::Statement query(aDatabase, apQuery); - bind_exec(query, std::forward(aArg)); - (void)std::initializer_list - { - ((void)reset_bind_exec(query, std::forward(aParams)), 0)... - }; -} - -/** - * \brief Convenience function to reset a statement and call bind_exec to - * bind new values to the statement and execute it - * - * This feature requires a c++14 capable compiler. - * - * @param apQuery Query to use - * @param aTuple Tuple to bind - */ -template -void reset_bind_exec(Statement& apQuery, TupleT&& aTuple) -{ - apQuery.reset(); - bind_exec(apQuery, std::forward(aTuple)); -} - -/** - * \brief Convenience function to bind values a the statement and execute it - * - * This feature requires a c++14 capable compiler. - * - * @param apQuery Query to use - * @param aTuple Tuple to bind - */ -template -void bind_exec(Statement& apQuery, TupleT&& aTuple) -{ - SQLite::bind(apQuery, std::forward(aTuple)); - while (apQuery.executeStep()) {} -} - -} // namespace SQLite - -#endif // c++14 diff --git a/ExternalLibraries/SQLiteCpp/SQLiteCpp.h b/ExternalLibraries/SQLiteCpp/SQLiteCpp.h deleted file mode 100644 index 8221fea2..00000000 --- a/ExternalLibraries/SQLiteCpp/SQLiteCpp.h +++ /dev/null @@ -1,45 +0,0 @@ -/** - * @file SQLiteCpp.h - * @ingroup SQLiteCpp - * @brief SQLiteC++ is a smart and simple C++ SQLite3 wrapper. This file is only "easy include" for other files. - * - * Include this main header file in your project to gain access to all functionality provided by the wrapper. - * - * Copyright (c) 2012-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ -/** - * @defgroup SQLiteCpp SQLiteC++ - * @brief SQLiteC++ is a smart and simple C++ SQLite3 wrapper. This file is only "easy include" for other files. - */ -#pragma once - - -// Include useful headers of SQLiteC++ -#include "SQLiteCppExport.h" -#include "Assertion.h" -#include "Exception.h" -#include "Database.h" -#include "Statement.h" -#include "Column.h" -#include "Transaction.h" - -/** - * @brief Version numbers for SQLiteC++ are provided in the same way as sqlite3.h - * - * The [SQLITECPP_VERSION] C preprocessor macro in the SQLiteC++.h header - * evaluates to a string literal that is the SQLite version in the - * format "X.Y.Z" where X is the major version number - * and Y is the minor version number and Z is the release number. - * - * The [SQLITECPP_VERSION_NUMBER] C preprocessor macro resolves to an integer - * with the value (X*1000000 + Y*1000 + Z) where X, Y, and Z are the same - * numbers used in [SQLITECPP_VERSION]. - * - * WARNING: shall always be updated in sync with PROJECT_VERSION in CMakeLists.txt - */ -#define SQLITECPP_VERSION "3.03.01" // 3.3.1 -#define SQLITECPP_VERSION_NUMBER 3003001 // 3.3.1 - diff --git a/ExternalLibraries/SQLiteCpp/SQLiteCppExport.h b/ExternalLibraries/SQLiteCpp/SQLiteCppExport.h deleted file mode 100644 index d6e9a06a..00000000 --- a/ExternalLibraries/SQLiteCpp/SQLiteCppExport.h +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @file SQLiteCppExport.h - * @ingroup SQLiteCpp - * @brief File with macros needed in the generation of Windows DLLs - * - * - * Copyright (c) 2012-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ - -#pragma once - -/* -* #define SQLITECPP_COMPILE_DLL to compile a DLL under Windows -* #define SQLITECPP_EXPORT to export symbols when creating the DLL, otherwise it defaults to importing symbols -*/ - -/* Windows DLL export/import */ -#if defined(_WIN32)&& !defined(__GNUC__) && defined(SQLITECPP_COMPILE_DLL) - #if SQLITECPP_DLL_EXPORT - #define SQLITECPP_API __declspec(dllexport) - #else - #define SQLITECPP_API __declspec(dllimport) - #endif -#else - #if __GNUC__ >= 4 - #define SQLITECPP_API __attribute__ ((visibility ("default"))) - #else - #define SQLITECPP_API - #endif -#endif - -#if defined(WIN32) && defined(SQLITECPP_COMPILE_DLL) - #pragma warning( disable : 4251 ) - #pragma warning( disable : 4275 ) -#endif diff --git a/ExternalLibraries/SQLiteCpp/Savepoint.h b/ExternalLibraries/SQLiteCpp/Savepoint.h deleted file mode 100644 index 8dd5e894..00000000 --- a/ExternalLibraries/SQLiteCpp/Savepoint.h +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @file Savepoint.h - * @ingroup SQLiteCpp - * @brief A Savepoint is a way to group multiple SQL statements into an atomic - * secured operation. Similar to a transaction while allowing child savepoints. - * - * Copyright (c) 2020 Kelvin Hammond (hammond.kelvin@gmail.com) - * Copyright (c) 2020-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt or - * copy at http://opensource.org/licenses/MIT) - */ -#pragma once - -#include -#include - -namespace SQLite -{ - -// Forward declaration -class Database; - -/** - * @brief RAII encapsulation of a SQLite Savepoint. - * - * SAVEPOINTs are a method of creating Transactions, similar to BEGIN and COMMIT, - * except that the SAVEPOINT and RELEASE commands are named and may be nested.. - * - * Resource Acquisition Is Initialization (RAII) means that the Savepoint - * begins in the constructor and is rolled back in the destructor (unless committed before), so that there is - * no need to worry about memory management or the validity of the underlying SQLite Connection. - * - * This method also offers big performances improvements compared to - * individually executed statements. - * - * Caveats: - * - * 1) Calling COMMIT or committing a parent transaction or RELEASE on a parent - * savepoint will cause this savepoint to be released. - * - * 2) Calling ROLLBACK TO or rolling back a parent savepoint will cause this - * savepoint to be rolled back. - * - * 3) This savepoint is not saved to the database until this and all savepoints - * or transaction in the savepoint stack have been released or committed. - * - * See also: https://sqlite.org/lang_savepoint.html - * - * Thread-safety: a Savepoint object shall not be shared by multiple threads, because: - * 1) in the SQLite "Thread Safe" mode, "SQLite can be safely used by multiple threads - * provided that no single database connection is used simultaneously in two or more threads." - * 2) the SQLite "Serialized" mode is not supported by SQLiteC++, - * because of the way it shares the underling SQLite precompiled statement - * in a custom shared pointer (See the inner class "Statement::Ptr"). - */ -class SQLITECPP_API Savepoint -{ -public: - /** - * @brief Begins the SQLite savepoint - * - * @param[in] aDatabase the SQLite Database Connection - * @param[in] aName the name of the Savepoint - * - * Exception is thrown in case of error, then the Savepoint is NOT - * initiated. - */ - Savepoint(Database& aDatabase, const std::string& aName); - - // Savepoint is non-copyable - Savepoint(const Savepoint&) = delete; - Savepoint& operator=(const Savepoint&) = delete; - - /** - * @brief Safely rollback the savepoint if it has not been committed. - */ - ~Savepoint(); - - /** - * @brief Commit and release the savepoint. - */ - void release(); - - /** - * @brief Rollback to the savepoint, but don't release it. - */ - void rollbackTo(); - // @deprecated same as rollbackTo(); - void rollback() { rollbackTo(); } - -private: - Database& mDatabase; ///< Reference to the SQLite Database Connection - std::string msName; ///< Name of the Savepoint - bool mbReleased = false; ///< True when release has been called -}; - -} // namespace SQLite diff --git a/ExternalLibraries/SQLiteCpp/Statement.h b/ExternalLibraries/SQLiteCpp/Statement.h deleted file mode 100644 index f7f5e4f7..00000000 --- a/ExternalLibraries/SQLiteCpp/Statement.h +++ /dev/null @@ -1,711 +0,0 @@ -/** - * @file Statement.h - * @ingroup SQLiteCpp - * @brief A prepared SQLite Statement is a compiled SQL query ready to be executed, pointing to a row of result. - * - * Copyright (c) 2012-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ -#pragma once - -#include "SQLiteCppExport.h" -#include "Exception.h" -#include "Utils.h" // SQLITECPP_PURE_FUNC - -#include -#include -#include - -// Forward declarations to avoid inclusion of in a header -struct sqlite3; -struct sqlite3_stmt; - -namespace SQLite -{ - -// Forward declaration -class Database; -class Column; - -SQLITECPP_API extern const int OK; ///< SQLITE_OK - -/** - * @brief RAII encapsulation of a prepared SQLite Statement. - * - * A Statement is a compiled SQL query ready to be executed step by step - * to provide results one row at a time. - * - * Resource Acquisition Is Initialization (RAII) means that the Statement - * is compiled in the constructor and finalized in the destructor, so that there is - * no need to worry about memory management or the validity of the underlying SQLite Statement. - * - * Thread-safety: a Statement object shall not be shared by multiple threads, because : - * 1) in the SQLite "Thread Safe" mode, "SQLite can be safely used by multiple threads - * provided that no single database connection is used simultaneously in two or more threads." - * 2) the SQLite "Serialized" mode is not supported by SQLiteC++, - * because of the way it shares the underling SQLite precompiled statement - * in a custom shared pointer (See the inner class "Statement::Ptr"). - */ -class SQLITECPP_API Statement -{ -public: - /** - * @brief Compile and register the SQL query for the provided SQLite Database Connection - * - * @param[in] aDatabase the SQLite Database Connection - * @param[in] apQuery an UTF-8 encoded query string - * - * Exception is thrown in case of error, then the Statement object is NOT constructed. - */ - Statement(const Database& aDatabase, const char* apQuery); - - /** - * @brief Compile and register the SQL query for the provided SQLite Database Connection - * - * @param[in] aDatabase the SQLite Database Connection - * @param[in] aQuery an UTF-8 encoded query string - * - * Exception is thrown in case of error, then the Statement object is NOT constructed. - */ - Statement(const Database& aDatabase, const std::string& aQuery) : - Statement(aDatabase, aQuery.c_str()) - {} - - // Statement is non-copyable - Statement(const Statement&) = delete; - Statement& operator=(const Statement&) = delete; - - // TODO: Change Statement move constructor to default - Statement(Statement&& aStatement) noexcept; - Statement& operator=(Statement&& aStatement) noexcept = default; - - /// Finalize and unregister the SQL query from the SQLite Database Connection. - /// The finalization will be done by the destructor of the last shared pointer - ~Statement() = default; - - /// Reset the statement to make it ready for a new execution by calling sqlite3_reset. - /// Throws an exception on error. - /// Call this function before any news calls to bind() if the statement was already executed before. - /// Calling reset() does not clear the bindings (see clearBindings()). - void reset(); - - /// Reset the statement. Returns the sqlite result code instead of throwing an exception on error. - int tryReset() noexcept; - - /** - * @brief Clears away all the bindings of a prepared statement. - * - * Contrary to the intuition of many, reset() does not reset the bindings on a prepared statement. - * Use this routine to reset all parameters to NULL. - */ - void clearBindings(); // throw(SQLite::Exception) - - //////////////////////////////////////////////////////////////////////////// - // Bind a value to a parameter of the SQL statement, - // in the form "?" (unnamed), "?NNN", ":VVV", "@VVV" or "$VVV". - // - // Can use the parameter index, starting from "1", to the higher NNN value, - // or the complete parameter name "?NNN", ":VVV", "@VVV" or "$VVV" - // (prefixed with the corresponding sign "?", ":", "@" or "$") - // - // Note that for text and blob values, the SQLITE_TRANSIENT flag is used, - // which tell the sqlite library to make its own copy of the data before the bind() call returns. - // This choice is done to prevent any common misuses, like passing a pointer to a - // dynamic allocated and temporary variable (a std::string for instance). - // This is under-optimized for static data (a static text define in code) - // as well as for dynamic allocated buffer which could be transfer to sqlite - // instead of being copied. - // => if you know what you are doing, use bindNoCopy() instead of bind() - - SQLITECPP_PURE_FUNC - int getIndex(const char * const apName) const; - - /** - * @brief Bind an int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - */ - void bind(const int aIndex, const int32_t aValue); - /** - * @brief Bind a 32bits unsigned int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - */ - void bind(const int aIndex, const uint32_t aValue); - /** - * @brief Bind a 64bits int value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - */ - void bind(const int aIndex, const int64_t aValue); - /** - * @brief Bind a double (64bits float) value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - */ - void bind(const int aIndex, const double aValue); - /** - * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use - */ - void bind(const int aIndex, const std::string& aValue); - /** - * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use - */ - void bind(const int aIndex, const char* apValue); - /** - * @brief Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use - */ - void bind(const int aIndex, const void* apValue, const int aSize); - /** - * @brief Bind a string value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1). - * - * The string can contain null characters as it is binded using its size. - * - * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. - */ - void bindNoCopy(const int aIndex, const std::string& aValue); - /** - * @brief Bind a text value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * Main usage is with null-terminated literal text (aka in code static strings) - * - * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. - */ - void bindNoCopy(const int aIndex, const char* apValue); - /** - * @brief Bind a binary blob value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. - */ - void bindNoCopy(const int aIndex, const void* apValue, const int aSize); - /** - * @brief Bind a NULL value to a parameter "?", "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @see clearBindings() to set all bound parameters to NULL. - */ - void bind(const int aIndex); - - /** - * @brief Bind an int value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - */ - void bind(const char* apName, const int32_t aValue) - { - bind(getIndex(apName), aValue); - } - /** - * @brief Bind a 32bits unsigned int value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - */ - void bind(const char* apName, const uint32_t aValue) - { - bind(getIndex(apName), aValue); - } - /** - * @brief Bind a 64bits int value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - */ - void bind(const char* apName, const int64_t aValue) - { - bind(getIndex(apName), aValue); - } - /** - * @brief Bind a double (64bits float) value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - */ - void bind(const char* apName, const double aValue) - { - bind(getIndex(apName), aValue); - } - /** - * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use - */ - void bind(const char* apName, const std::string& aValue) - { - bind(getIndex(apName), aValue); - } - /** - * @brief Bind a text value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use - */ - void bind(const char* apName, const char* apValue) - { - bind(getIndex(apName), apValue); - } - /** - * @brief Bind a binary blob value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use - */ - void bind(const char* apName, const void* apValue, const int aSize) - { - bind(getIndex(apName), apValue, aSize); - } - /** - * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * The string can contain null characters as it is binded using its size. - * - * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. - */ - void bindNoCopy(const char* apName, const std::string& aValue) - { - bindNoCopy(getIndex(apName), aValue); - } - /** - * @brief Bind a text value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * Main usage is with null-terminated literal text (aka in code static strings) - * - * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. - */ - void bindNoCopy(const char* apName, const char* apValue) - { - bindNoCopy(getIndex(apName), apValue); - } - /** - * @brief Bind a binary blob value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. - */ - void bindNoCopy(const char* apName, const void* apValue, const int aSize) - { - bindNoCopy(getIndex(apName), apValue, aSize); - } - /** - * @brief Bind a NULL value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @see clearBindings() to set all bound parameters to NULL. - */ - void bind(const char* apName) // bind NULL value - { - bind(getIndex(apName)); - } - - - /** - * @brief Bind an int value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - */ - void bind(const std::string& aName, const int32_t aValue) - { - bind(aName.c_str(), aValue); - } - /** - * @brief Bind a 32bits unsigned int value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - */ - void bind(const std::string& aName, const uint32_t aValue) - { - bind(aName.c_str(), aValue); - } - /** - * @brief Bind a 64bits int value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - */ - void bind(const std::string& aName, const int64_t aValue) - { - bind(aName.c_str(), aValue); - } - /** - * @brief Bind a double (64bits float) value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - */ - void bind(const std::string& aName, const double aValue) - { - bind(aName.c_str(), aValue); - } - /** - * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use - */ - void bind(const std::string& aName, const std::string& aValue) - { - bind(aName.c_str(), aValue); - } - /** - * @brief Bind a text value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use - */ - void bind(const std::string& aName, const char* apValue) - { - bind(aName.c_str(), apValue); - } - /** - * @brief Bind a binary blob value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @note Uses the SQLITE_TRANSIENT flag, making a copy of the data, for SQLite internal use - */ - void bind(const std::string& aName, const void* apValue, const int aSize) - { - bind(aName.c_str(), apValue, aSize); - } - /** - * @brief Bind a string value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * The string can contain null characters as it is binded using its size. - * - * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. - */ - void bindNoCopy(const std::string& aName, const std::string& aValue) - { - bindNoCopy(aName.c_str(), aValue); - } - /** - * @brief Bind a text value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * Main usage is with null-terminated literal text (aka in code static strings) - * - * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. - */ - void bindNoCopy(const std::string& aName, const char* apValue) - { - bindNoCopy(aName.c_str(), apValue); - } - /** - * @brief Bind a binary blob value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @warning Uses the SQLITE_STATIC flag, avoiding a copy of the data. The string must remains unchanged while executing the statement. - */ - void bindNoCopy(const std::string& aName, const void* apValue, const int aSize) - { - bindNoCopy(aName.c_str(), apValue, aSize); - } - /** - * @brief Bind a NULL value to a named parameter "?NNN", ":VVV", "@VVV" or "$VVV" in the SQL prepared statement (aIndex >= 1) - * - * @see clearBindings() to set all bound parameters to NULL. - */ - void bind(const std::string& aName) // bind NULL value - { - bind(aName.c_str()); - } - - //////////////////////////////////////////////////////////////////////////// - - /** - * @brief Execute a step of the prepared query to fetch one row of results. - * - * While true is returned, a row of results is available, and can be accessed - * through the getColumn() method - * - * @see exec() execute a one-step prepared statement with no expected result - * @see tryExecuteStep() try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code. - * @see Database::exec() is a shortcut to execute one or multiple statements without results - * - * @return - true (SQLITE_ROW) if there is another row ready : you can call getColumn(N) to get it - * then you have to call executeStep() again to fetch more rows until the query is finished - * - false (SQLITE_DONE) if the query has finished executing : there is no (more) row of result - * (case of a query with no result, or after N rows fetched successfully) - * - * @throw SQLite::Exception in case of error - */ - bool executeStep(); - - /** - * @brief Try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code. - * - * - * - * @see exec() execute a one-step prepared statement with no expected result - * @see executeStep() execute a step of the prepared query to fetch one row of results - * @see Database::exec() is a shortcut to execute one or multiple statements without results - * - * @return the sqlite result code. - */ - int tryExecuteStep() noexcept; - - /** - * @brief Execute a one-step query with no expected result, and return the number of changes. - * - * This method is useful for any kind of statements other than the Data Query Language (DQL) "SELECT" : - * - Data Definition Language (DDL) statements "CREATE", "ALTER" and "DROP" - * - Data Manipulation Language (DML) statements "INSERT", "UPDATE" and "DELETE" - * - Data Control Language (DCL) statements "GRANT", "REVOKE", "COMMIT" and "ROLLBACK" - * - * It is similar to Database::exec(), but using a precompiled statement, it adds : - * - the ability to bind() arguments to it (best way to insert data), - * - reusing it allows for better performances (efficient for multiple insertion). - * - * @see executeStep() execute a step of the prepared query to fetch one row of results - * @see tryExecuteStep() try to execute a step of the prepared query to fetch one row of results, returning the sqlite result code. - * @see Database::exec() is a shortcut to execute one or multiple statements without results - * - * @return number of row modified by this SQL statement (INSERT, UPDATE or DELETE) - * - * @throw SQLite::Exception in case of error, or if row of results are returned while they are not expected! - */ - int exec(); - - //////////////////////////////////////////////////////////////////////////// - - /** - * @brief Return a copy of the column data specified by its index - * - * Can be used to access the data of the current row of result when applicable, - * while the executeStep() method returns true. - * - * Throw an exception if there is no row to return a Column from: - * - if provided index is out of bound - * - before any executeStep() call - * - after the last executeStep() returned false - * - after a reset() call - * - * Throw an exception if the specified index is out of the [0, getColumnCount()) range. - * - * @param[in] aIndex Index of the column, starting at 0 - * - * @note This method is not const, reflecting the fact that the returned Column object will - * share the ownership of the underlying sqlite3_stmt. - * - * @warning The resulting Column object must not be memorized "as-is". - * Is is only a wrapper around the current result row, so it is only valid - * while the row from the Statement remains valid, that is only until next executeStep() call. - * Thus, you should instead extract immediately its data (getInt(), getText()...) - * and use or copy this data for any later usage. - */ - Column getColumn(const int aIndex) const; - - /** - * @brief Return a copy of the column data specified by its column name (less efficient than using an index) - * - * Can be used to access the data of the current row of result when applicable, - * while the executeStep() method returns true. - * - * Throw an exception if there is no row to return a Column from : - * - if provided name is not one of the aliased column names - * - before any executeStep() call - * - after the last executeStep() returned false - * - after a reset() call - * - * Throw an exception if the specified name is not an on of the aliased name of the columns in the result. - * - * @param[in] apName Aliased name of the column, that is, the named specified in the query (not the original name) - * - * @note Uses a map of column names to indexes, build on first call. - * - * @note This method is not const, reflecting the fact that the returned Column object will - * share the ownership of the underlying sqlite3_stmt. - * - * @warning The resulting Column object must not be memorized "as-is". - * Is is only a wrapper around the current result row, so it is only valid - * while the row from the Statement remains valid, that is only until next executeStep() call. - * Thus, you should instead extract immediately its data (getInt(), getText()...) - * and use or copy this data for any later usage. - * - * Throw an exception if the specified name is not one of the aliased name of the columns in the result. - */ - Column getColumn(const char* apName) const; - -#if __cplusplus >= 201402L || (defined(_MSC_VER) && _MSC_VER >= 1900) // c++14: Visual Studio 2015 - /** - * @brief Return an instance of T constructed from copies of the first N columns - * - * Can be used to access the data of the current row of result when applicable, - * while the executeStep() method returns true. - * - * Throw an exception if there is no row to return a Column from: - * - if provided column count is out of bound - * - before any executeStep() call - * - after the last executeStep() returned false - * - after a reset() call - * - * Throw an exception if the specified column count is out of the [0, getColumnCount()) range. - * - * @tparam T Object type to construct - * @tparam N Number of columns - * - * @note Requires std=C++14 - */ - template - T getColumns(); - -private: - /** - * @brief Helper function used by getColumns to expand an integer_sequence used to generate - * the required Column objects - */ - template - T getColumns(const std::integer_sequence); - -public: -#endif - - /** - * @brief Test if the column value is NULL - * - * @param[in] aIndex Index of the column, starting at 0 - * - * @return true if the column value is NULL - * - * Throw an exception if the specified index is out of the [0, getColumnCount()) range. - */ - bool isColumnNull(const int aIndex) const; - - /** - * @brief Test if the column value is NULL - * - * @param[in] apName Aliased name of the column, that is, the named specified in the query (not the original name) - * - * @return true if the column value is NULL - * - * Throw an exception if the specified name is not one of the aliased name of the columns in the result. - */ - bool isColumnNull(const char* apName) const; - - /** - * @brief Return a pointer to the named assigned to the specified result column (potentially aliased) - * - * @param[in] aIndex Index of the column in the range [0, getColumnCount()). - * - * @see getColumnOriginName() to get original column name (not aliased) - * - * Throw an exception if the specified index is out of the [0, getColumnCount()) range. - */ - const char* getColumnName(const int aIndex) const; - -#ifdef SQLITE_ENABLE_COLUMN_METADATA - /** - * @brief Return a pointer to the table column name that is the origin of the specified result column - * - * Require definition of the SQLITE_ENABLE_COLUMN_METADATA preprocessor macro : - * - when building the SQLite library itself (which is the case for the Debian libsqlite3 binary for instance), - * - and also when compiling this wrapper. - * - * Throw an exception if the specified index is out of the [0, getColumnCount()) range. - */ - const char* getColumnOriginName(const int aIndex) const; -#endif - - /** - * @brief Return the index of the specified (potentially aliased) column name - * - * @param[in] apName Aliased name of the column, that is, the named specified in the query (not the original name) - * - * @note Uses a map of column names to indexes, build on first call. - * - * Throw an exception if the specified name is not known. - */ - int getColumnIndex(const char* apName) const; - - - /** - * @brief Return the declared type of the specified result column for a SELECT statement. - * - * This is the type given at creation of the column and not the actual data type. - * SQLite stores data types dynamically for each value and not per column. - * - * @param[in] aIndex Index of the column in the range [0, getColumnCount()). - * - * Throw an exception if the type can't be determined because: - * - the specified index is out of the [0, getColumnCount()) range - * - the statement is not a SELECT query - * - the column at aIndex is not a table column but an expression or subquery - */ - const char * getColumnDeclaredType(const int aIndex) const; - - - /// Get number of rows modified by last INSERT, UPDATE or DELETE statement (not DROP table). - int getChanges() const noexcept; - - - //////////////////////////////////////////////////////////////////////////// - - /// Return the UTF-8 SQL Query. - const std::string& getQuery() const - { - return mQuery; - } - - // Return a UTF-8 string containing the SQL text of prepared statement with bound parameters expanded. - std::string getExpandedSQL() const; - - /// Return the number of columns in the result set returned by the prepared statement - int getColumnCount() const - { - return mColumnCount; - } - /// true when a row has been fetched with executeStep() - bool hasRow() const - { - return mbHasRow; - } - /// true when the last executeStep() had no more row to fetch - bool isDone() const - { - return mbDone; - } - - /// Return the number of bind parameters in the statement - int getBindParameterCount() const noexcept; - - /// Return the numeric result code for the most recent failed API call (if any). - int getErrorCode() const noexcept; - /// Return the extended numeric result code for the most recent failed API call (if any). - int getExtendedErrorCode() const noexcept; - /// Return UTF-8 encoded English language explanation of the most recent failed API call (if any). - const char* getErrorMsg() const noexcept; - - /// Shared pointer to SQLite Prepared Statement Object - using TStatementPtr = std::shared_ptr; - -private: - /** - * @brief Check if a return code equals SQLITE_OK, else throw a SQLite::Exception with the SQLite error message - * - * @param[in] aRet SQLite return code to test against the SQLITE_OK expected value - */ - void check(const int aRet) const - { - if (SQLite::OK != aRet) - { - throw SQLite::Exception(mpSQLite, aRet); - } - } - - /** - * @brief Check if there is a row of result returned by executeStep(), else throw a SQLite::Exception. - */ - void checkRow() const - { - if (false == mbHasRow) - { - throw SQLite::Exception("No row to get a column from. executeStep() was not called, or returned false."); - } - } - - /** - * @brief Check if there is a Column index is in the range of columns in the result. - */ - void checkIndex(const int aIndex) const - { - if ((aIndex < 0) || (aIndex >= mColumnCount)) - { - throw SQLite::Exception("Column index out of range."); - } - } - - /** - * @brief Prepare statement object. - * - * @return Shared pointer to prepared statement object - */ - TStatementPtr prepareStatement(); - - /** - * @brief Return a prepared statement object. - * - * Throw an exception if the statement object was not prepared. - * @return raw pointer to Prepared Statement Object - */ - sqlite3_stmt* getPreparedStatement() const; - - std::string mQuery; //!< UTF-8 SQL Query - sqlite3* mpSQLite; //!< Pointer to SQLite Database Connection Handle - TStatementPtr mpPreparedStatement; //!< Shared Pointer to the prepared SQLite Statement Object - int mColumnCount = 0; //!< Number of columns in the result of the prepared statement - bool mbHasRow = false; //!< true when a row has been fetched with executeStep() - bool mbDone = false; //!< true when the last executeStep() had no more row to fetch - - /// Map of columns index by name (mutable so getColumnIndex can be const) - mutable std::map mColumnNames; -}; - -} // namespace SQLite diff --git a/ExternalLibraries/SQLiteCpp/Transaction.h b/ExternalLibraries/SQLiteCpp/Transaction.h deleted file mode 100644 index 5e7eba85..00000000 --- a/ExternalLibraries/SQLiteCpp/Transaction.h +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @file Transaction.h - * @ingroup SQLiteCpp - * @brief A Transaction is way to group multiple SQL statements into an atomic secured operation. - * - * Copyright (c) 2012-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ -#pragma once - -#include "SQLiteCppExport.h" -#include "Exception.h" - -namespace SQLite -{ - -// Forward declaration -class Database; - -/** - * @brief Transaction behaviors when opening an SQLite transaction. - * Names correspond directly to the behavior. - */ -enum class TransactionBehavior { - DEFERRED, - IMMEDIATE, - EXCLUSIVE, -}; - -/** - * @brief RAII encapsulation of a SQLite Transaction. - * - * A Transaction is a way to group multiple SQL statements into an atomic secured operation; - * either it succeeds, with all the changes committed to the database file, - * or if it fails, all the changes are rolled back to the initial state. - * - * Resource Acquisition Is Initialization (RAII) means that the Transaction - * begins in the constructor and is rolled back in the destructor (unless committed before), so that there is - * no need to worry about memory management or the validity of the underlying SQLite Connection. - * - * This method also offers big performances improvements compared to individually executed statements. - * - * Thread-safety: a Transaction object shall not be shared by multiple threads, because : - * 1) in the SQLite "Thread Safe" mode, "SQLite can be safely used by multiple threads - * provided that no single database connection is used simultaneously in two or more threads." - * 2) the SQLite "Serialized" mode is not supported by SQLiteC++, - * because of the way it shares the underling SQLite precompiled statement - * in a custom shared pointer (See the inner class "Statement::Ptr"). - */ -class SQLITECPP_API Transaction -{ -public: - /** - * @brief Begins the SQLite transaction using the default transaction behavior. - * - * @param[in] aDatabase the SQLite Database Connection - * - * Exception is thrown in case of error, then the Transaction is NOT initiated. - */ - explicit Transaction(Database& aDatabase); - - /** - * @brief Begins the SQLite transaction with the specified behavior. - * - * @param[in] aDatabase the SQLite Database Connection - * @param[in] behavior the requested transaction behavior - * - * Exception is thrown in case of error, then the Transaction is NOT initiated. - */ - explicit Transaction(Database& aDatabase, TransactionBehavior behavior); - - // Transaction is non-copyable - Transaction(const Transaction&) = delete; - Transaction& operator=(const Transaction&) = delete; - - /** - * @brief Safely rollback the transaction if it has not been committed. - */ - ~Transaction(); - - /** - * @brief Commit the transaction. - */ - void commit(); - - /** - * @brief Rollback the transaction - */ - void rollback(); - -private: - Database& mDatabase; ///< Reference to the SQLite Database Connection - bool mbCommited = false; ///< True when commit has been called -}; - -} // namespace SQLite diff --git a/ExternalLibraries/SQLiteCpp/Utils.h b/ExternalLibraries/SQLiteCpp/Utils.h deleted file mode 100644 index 7845ec0e..00000000 --- a/ExternalLibraries/SQLiteCpp/Utils.h +++ /dev/null @@ -1,31 +0,0 @@ -/** - * @file Utils.h - * @ingroup SQLiteCpp - * @brief Definition of the SQLITECPP_PURE_FUNC macro. - * - * Copyright (c) 2012-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ -#pragma once - -// macro taken from https://github.com/nemequ/hedley/blob/master/hedley.h that was in public domain at this time -#if defined(__GNUC__) || defined(__GNUG__) || defined(__clang__) ||\ -(defined(__INTEL_COMPILER) && __INTEL_COMPILER > 1600) ||\ -(defined(__ARMCC_VERSION) && __ARMCC_VERSION > 4010000) ||\ -(\ - defined(__TI_COMPILER_VERSION__) && (\ - __TI_COMPILER_VERSION__ > 8003000 ||\ - (__TI_COMPILER_VERSION__ > 7003000 && defined(__TI_GNU_ATTRIBUTE_SUPPORT__))\ - )\ -) -#if defined(__has_attribute) -#if !defined(SQLITECPP_PURE_FUNC) && __has_attribute(pure) -#define SQLITECPP_PURE_FUNC __attribute__((pure)) -#endif -#endif -#endif -#if !defined(SQLITECPP_PURE_FUNC) -#define SQLITECPP_PURE_FUNC -#endif diff --git a/ExternalLibraries/SQLiteCpp/VariadicBind.h b/ExternalLibraries/SQLiteCpp/VariadicBind.h deleted file mode 100644 index 3261da4d..00000000 --- a/ExternalLibraries/SQLiteCpp/VariadicBind.h +++ /dev/null @@ -1,98 +0,0 @@ -/** - * @file VariadicBind.h - * @ingroup SQLiteCpp - * @brief Convenience function for Statement::bind(...) - * - * Copyright (c) 2016 Paul Dreik (github@pauldreik.se) - * Copyright (c) 2016-2023 Sebastien Rombauts (sebastien.rombauts@gmail.com) - * Copyright (c) 2019 Maximilian Bachmann (contact@maxbachmann.de) - * - * Distributed under the MIT License (MIT) (See accompanying file LICENSE.txt - * or copy at http://opensource.org/licenses/MIT) - */ -#pragma once - -#include - -#if (__cplusplus >= 201402L) || ( defined(_MSC_VER) && (_MSC_VER >= 1900) ) // c++14: Visual Studio 2015 -#include -#endif // c++14 - -/// @cond -#include -#include - -namespace SQLite -{ -/// @endcond - -/** - * \brief Convenience function for calling Statement::bind(...) once for each argument given. - * - * This takes care of incrementing the index between each calls to bind. - * - * This feature requires a c++11 capable compiler. - * - * \code{.cpp} - * SQLite::Statement stm("SELECT * FROM MyTable WHERE colA>? && colB=? && colC -void bind(SQLite::Statement& query, const Args& ... args) -{ - int pos = 0; - (void)std::initializer_list{ - ((void)query.bind(++pos, std::forward(args)), 0)... - }; -} - -#if (__cplusplus >= 201402L) || ( defined(_MSC_VER) && (_MSC_VER >= 1900) ) // c++14: Visual Studio 2015 - -/** - * \brief Convenience function for calling Statement::bind(...) once for each parameter of a tuple, - * by forwarding them to the variadic template - * - * This feature requires a c++14 capable compiler. - * - * \code{.cpp} - * SQLite::Statement stm("SELECT * FROM MyTable WHERE colA>? && colB=? && colC -void bind(SQLite::Statement& query, const std::tuple &tuple) -{ - bind(query, tuple, std::index_sequence_for()); -} - -/** - * \brief Convenience function for calling Statement::bind(...) once for each parameter of a tuple, - * by forwarding them to the variadic template. This function is just needed to convert the tuples - * to parameter packs - * - * This feature requires a c++14 capable compiler. - * - * @param query statement - * @param tuple tuple with values to bind - */ -template -void bind(SQLite::Statement& query, const std::tuple &tuple, std::index_sequence) -{ - bind(query, std::get(tuple)...); -} -#endif // c++14 - -} // namespace SQLite diff --git a/ExternalLibraries/visit_struct/visit_struct.hpp b/ExternalLibraries/visit_struct/visit_struct.hpp index a4f6953e..4d4bbc6c 100644 --- a/ExternalLibraries/visit_struct/visit_struct.hpp +++ b/ExternalLibraries/visit_struct/visit_struct.hpp @@ -18,7 +18,7 @@ // Library version #define VISIT_STRUCT_VERSION_MAJOR 1 -#define VISIT_STRUCT_VERSION_MINOR 1 +#define VISIT_STRUCT_VERSION_MINOR 2 #define VISIT_STRUCT_VERSION_PATCH 0 #define VISIT_STRUCT_STRING_HELPER(X) #X @@ -47,21 +47,33 @@ # endif # endif +// After C++20 we can use __VA_OPT__ and can also visit empty struct +# ifndef VISIT_STRUCT_PP_HAS_VA_OPT +# if (defined _MSVC_TRADITIONAL && !_MSVC_TRADITIONAL) || (defined __cplusplus && __cplusplus >= 202000L) +# define VISIT_STRUCT_PP_HAS_VA_OPT true +# else +# define VISIT_STRUCT_PP_HAS_VA_OPT false +# endif +# endif + namespace visit_struct { namespace traits { // Primary template which is specialized to register a type -template +// The context parameter is set when a user wants to register multiple visitation patterns, +// to include or exclude some field in different contexts. +template struct visitable; // Helper template which checks if a type is registered -template +template struct is_visitable : std::false_type {}; -template +template struct is_visitable::value>::type> + CONTEXT, + typename std::enable_if::value>::type> : std::true_type {}; // Helper template which removes cv and reference from a type (saves some typing) @@ -92,6 +104,8 @@ struct accessor { VISIT_STRUCT_CONSTEXPR auto operator()(T && t) const -> decltype(std::forward(t).*ptr) { return std::forward(t).*ptr; } + + static VISIT_STRUCT_CONSTEXPR const auto value = ptr; }; // @@ -287,6 +301,204 @@ VISIT_STRUCT_CONSTEXPR auto get_name(S &&) -> decltype(get_name()) { return get_name(); } +// Alternate visitation patterns can be registered using VISITABLE_STRUCT_IN_CONTEXT. +// Then, use visit_struct::context::for_each and similar to refer to special contexts. +template +struct context { + + // Return number of fields in a visitable struct + template + VISIT_STRUCT_CONSTEXPR static std::size_t field_count() + { + return traits::visitable, CONTEXT>::field_count; + } + + template + VISIT_STRUCT_CONSTEXPR static std::size_t field_count(S &&) { return field_count(); } + + + // apply_visitor (one struct instance) + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto apply_visitor(V && v, S && s) -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value + >::type + { + traits::visitable, CONTEXT>::apply(std::forward(v), std::forward(s)); + } + + // apply_visitor (two struct instances) + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto apply_visitor(V && v, S1 && s1, S2 && s2) -> + typename std::enable_if< + traits::is_visitable< + traits::clean_t::type>, + CONTEXT + >::value + >::type + { + using common_S = typename traits::common_type::type; + traits::visitable, CONTEXT>::apply(std::forward(v), + std::forward(s1), + std::forward(s2)); + } + + // for_each (Alternate syntax for apply_visitor, reverses order of arguments) + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto for_each(S && s, V && v) -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value + >::type + { + traits::visitable, CONTEXT>::apply(std::forward(v), std::forward(s)); + } + + // for_each with two structure instances + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto for_each(S1 && s1, S2 && s2, V && v) -> + typename std::enable_if< + traits::is_visitable< + traits::clean_t::type>, + CONTEXT + >::value + >::type + { + using common_S = typename traits::common_type::type; + traits::visitable, CONTEXT>::apply(std::forward(v), + std::forward(s1), + std::forward(s2)); + } + + // Visit the types (visit_struct::type_c<...>) of the registered members + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto visit_types(V && v) -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value + >::type + { + traits::visitable, CONTEXT>::visit_types(std::forward(v)); + } + + // Visit the member pointers (&S::a) of the registered members + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto visit_pointers(V && v) -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value + >::type + { + traits::visitable, CONTEXT>::visit_pointers(std::forward(v)); + } + + // Visit the accessors (function objects) of the registered members + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto visit_accessors(V && v) -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value + >::type + { + traits::visitable, CONTEXT>::visit_accessors(std::forward(v)); + } + + + // Apply visitor (with no instances) + // This calls visit_pointers, for backwards compat reasons + template + VISIT_STRUCT_CXX14_CONSTEXPR static auto apply_visitor(V && v) -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value + >::type + { + visit_struct::visit_pointers(std::forward(v)); + } + + + // Get value by index (like std::get for tuples) + template + VISIT_STRUCT_CONSTEXPR static auto get(S && s) -> + typename std::enable_if< + traits::is_visitable>::value, + decltype(traits::visitable, CONTEXT>::get_value(std::integral_constant{}, std::forward(s))) + >::type + { + return traits::visitable, CONTEXT>::get_value(std::integral_constant{}, std::forward(s)); + } + + // Get name of field, by index + template + VISIT_STRUCT_CONSTEXPR static auto get_name() -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value, + decltype(traits::visitable, CONTEXT>::get_name(std::integral_constant{})) + >::type + { + return traits::visitable, CONTEXT>::get_name(std::integral_constant{}); + } + + template + VISIT_STRUCT_CONSTEXPR static auto get_name(S &&) -> decltype(get_name()) { + return get_name(); + } + + // Get member pointer, by index + template + VISIT_STRUCT_CONSTEXPR static auto get_pointer() -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value, + decltype(traits::visitable, CONTEXT>::get_pointer(std::integral_constant{})) + >::type + { + return traits::visitable, CONTEXT>::get_pointer(std::integral_constant{}); + } + + template + VISIT_STRUCT_CONSTEXPR static auto get_pointer(S &&) -> decltype(get_pointer()) { + return get_pointer(); + } + + // Get member accessor, by index + template + VISIT_STRUCT_CONSTEXPR static auto get_accessor() -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value, + decltype(traits::visitable, CONTEXT>::get_accessor(std::integral_constant{})) + >::type + { + return traits::visitable, CONTEXT>::get_accessor(std::integral_constant{}); + } + + template + VISIT_STRUCT_CONSTEXPR static auto get_accessor(S &&) -> decltype(get_accessor()) { + return get_accessor(); + } + + // Get type, by index + template + struct type_at_s { + using type_c = decltype(traits::visitable, CONTEXT>::type_at(std::integral_constant{})); + using type = typename type_c::type; + }; + + template + using type_at = typename type_at_s::type; + + // Get name of structure + template + VISIT_STRUCT_CONSTEXPR static auto get_name() -> + typename std::enable_if< + traits::is_visitable, CONTEXT>::value, + decltype(traits::visitable, CONTEXT>::get_name()) + >::type + { + return traits::visitable, CONTEXT>::get_name(); + } + + template + VISIT_STRUCT_CONSTEXPR static auto get_name(S &&) -> decltype(get_name()) { + return get_name(); + } +}; + + /*** * To implement the VISITABLE_STRUCT macro, we need a map-macro, which can take * the name of a macro and some other arguments, and apply that macro to each other argument. @@ -310,14 +522,25 @@ static VISIT_STRUCT_CONSTEXPR const int max_visitable_members = 69; #define VISIT_STRUCT_EXPAND(x) x #define VISIT_STRUCT_PP_ARG_N( \ - _1, _2, _3, _4, _5, _6, _7, _8, _9, _10,\ + _0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10,\ _11, _12, _13, _14, _15, _16, _17, _18, _19, _20,\ _21, _22, _23, _24, _25, _26, _27, _28, _29, _30,\ _31, _32, _33, _34, _35, _36, _37, _38, _39, _40,\ _41, _42, _43, _44, _45, _46, _47, _48, _49, _50,\ _51, _52, _53, _54, _55, _56, _57, _58, _59, _60,\ _61, _62, _63, _64, _65, _66, _67, _68, _69, N, ...) N -#define VISIT_STRUCT_PP_NARG(...) VISIT_STRUCT_EXPAND(VISIT_STRUCT_PP_ARG_N(__VA_ARGS__, \ + +#if VISIT_STRUCT_PP_HAS_VA_OPT + #define VISIT_STRUCT_PP_NARG(...) VISIT_STRUCT_EXPAND(VISIT_STRUCT_PP_ARG_N(0 __VA_OPT__(,) __VA_ARGS__, \ + 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, \ + 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \ + 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \ + 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \ + 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \ + 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \ + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)) +#else + #define VISIT_STRUCT_PP_NARG(...) VISIT_STRUCT_EXPAND(VISIT_STRUCT_PP_ARG_N(0, __VA_ARGS__, \ 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, \ 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \ 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \ @@ -325,6 +548,7 @@ static VISIT_STRUCT_CONSTEXPR const int max_visitable_members = 69; 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \ 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \ 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)) +#endif /* need extra level to force extra eval */ #define VISIT_STRUCT_CONCAT_(a,b) a ## b @@ -402,7 +626,11 @@ static VISIT_STRUCT_CONSTEXPR const int max_visitable_members = 69; #define VISIT_STRUCT_APPLYF69(f,_1,_2,_3,_4,_5,_6,_7,_8,_9,_10,_11,_12,_13,_14,_15,_16,_17,_18,_19,_20,_21,_22,_23,_24,_25,_26,_27,_28,_29,_30,_31,_32,_33,_34,_35,_36,_37,_38,_39,_40,_41,_42,_43,_44,_45,_46,_47,_48,_49,_50,_51,_52,_53,_54,_55,_56,_57,_58,_59,_60,_61,_62,_63,_64,_65,_66,_67,_68,_69) f(_1) f(_2) f(_3) f(_4) f(_5) f(_6) f(_7) f(_8) f(_9) f(_10) f(_11) f(_12) f(_13) f(_14) f(_15) f(_16) f(_17) f(_18) f(_19) f(_20) f(_21) f(_22) f(_23) f(_24) f(_25) f(_26) f(_27) f(_28) f(_29) f(_30) f(_31) f(_32) f(_33) f(_34) f(_35) f(_36) f(_37) f(_38) f(_39) f(_40) f(_41) f(_42) f(_43) f(_44) f(_45) f(_46) f(_47) f(_48) f(_49) f(_50) f(_51) f(_52) f(_53) f(_54) f(_55) f(_56) f(_57) f(_58) f(_59) f(_60) f(_61) f(_62) f(_63) f(_64) f(_65) f(_66) f(_67) f(_68) f(_69) #define VISIT_STRUCT_APPLY_F_(M, ...) VISIT_STRUCT_EXPAND(M(__VA_ARGS__)) -#define VISIT_STRUCT_PP_MAP(f, ...) VISIT_STRUCT_EXPAND(VISIT_STRUCT_APPLY_F_(VISIT_STRUCT_CONCAT(VISIT_STRUCT_APPLYF, VISIT_STRUCT_PP_NARG(__VA_ARGS__)), f, __VA_ARGS__)) +#if VISIT_STRUCT_PP_HAS_VA_OPT + #define VISIT_STRUCT_PP_MAP(f, ...) VISIT_STRUCT_EXPAND(VISIT_STRUCT_APPLY_F_(VISIT_STRUCT_CONCAT(VISIT_STRUCT_APPLYF, VISIT_STRUCT_PP_NARG(__VA_ARGS__)), f __VA_OPT__(,) __VA_ARGS__)) +#else + #define VISIT_STRUCT_PP_MAP(f, ...) VISIT_STRUCT_EXPAND(VISIT_STRUCT_APPLY_F_(VISIT_STRUCT_CONCAT(VISIT_STRUCT_APPLYF, VISIT_STRUCT_PP_NARG(__VA_ARGS__)), f, __VA_ARGS__)) +#endif /*** End generated code ***/ @@ -534,6 +762,66 @@ struct visitable { } \ static_assert(true, "") +#define VISITABLE_STRUCT_IN_CONTEXT(CONTEXT, STRUCT_NAME, ...) \ +namespace visit_struct { \ +namespace traits { \ + \ +template <> \ +struct visitable { \ + \ + using this_type = STRUCT_NAME; \ + \ + static VISIT_STRUCT_CONSTEXPR auto get_name() \ + -> decltype(#STRUCT_NAME) { \ + return #STRUCT_NAME; \ + } \ + \ + static VISIT_STRUCT_CONSTEXPR const std::size_t field_count = 0 \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_FIELD_COUNT, __VA_ARGS__); \ + \ + template \ + VISIT_STRUCT_CXX14_CONSTEXPR static void apply(V && visitor, S && struct_instance) \ + { \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER, __VA_ARGS__) \ + } \ + \ + template \ + VISIT_STRUCT_CXX14_CONSTEXPR static void apply(V && visitor, S1 && s1, S2 && s2) \ + { \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_PAIR, __VA_ARGS__) \ + } \ + \ + template \ + VISIT_STRUCT_CXX14_CONSTEXPR static void visit_pointers(V && visitor) \ + { \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_PTR, __VA_ARGS__) \ + } \ + \ + template \ + VISIT_STRUCT_CXX14_CONSTEXPR static void visit_types(V && visitor) \ + { \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_TYPE, __VA_ARGS__) \ + } \ + \ + template \ + VISIT_STRUCT_CXX14_CONSTEXPR static void visit_accessors(V && visitor) \ + { \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MEMBER_HELPER_ACC, __VA_ARGS__) \ + } \ + \ + struct fields_enum { \ + enum index { __VA_ARGS__ }; \ + }; \ + \ + VISIT_STRUCT_PP_MAP(VISIT_STRUCT_MAKE_GETTERS, __VA_ARGS__) \ + \ + static VISIT_STRUCT_CONSTEXPR const bool value = true; \ +}; \ + \ +} \ +} \ +static_assert(true, "") + } // end namespace visit_struct #endif // VISIT_STRUCT_HPP_INCLUDED diff --git a/ExternalLibraries/visit_struct/visit_struct_boost_fusion.hpp b/ExternalLibraries/visit_struct/visit_struct_boost_fusion.hpp index 6f511523..2cf53908 100644 --- a/ExternalLibraries/visit_struct/visit_struct_boost_fusion.hpp +++ b/ExternalLibraries/visit_struct/visit_struct_boost_fusion.hpp @@ -9,11 +9,8 @@ #include #include -#include -#include -#include +#include #include -#include #include @@ -130,7 +127,7 @@ struct visitable::value >; using fv_t = fusion_visitor(v)), decltype(std::forward(t))>; fv_t fv{std::forward(v), std::forward(t)}; - fusion::for_each(Indices(), fv); + fusion::for_each(Indices(), std::move(fv)); } template diff --git a/MainServer/CMakeLists.txt b/MainServer/CMakeLists.txt new file mode 100644 index 00000000..6ee68da7 --- /dev/null +++ b/MainServer/CMakeLists.txt @@ -0,0 +1,65 @@ +cmake_minimum_required(VERSION 3.16) + +project(MainServer LANGUAGES CXX) + +file(GLOB_RECURSE MAIN_SOURCES CONFIGURE_DEPENDS + ${CMAKE_CURRENT_LIST_DIR}/*.cpp +) + +add_executable(MainServer + ${MAIN_SOURCES} +) + +target_include_directories(MainServer + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/include +) + +find_path(JWT_CPP_INCLUDE_DIRS "jwt-cpp/base.h") +target_include_directories(MainServer PUBLIC ${JWT_CPP_INCLUDE_DIRS}) + +target_link_libraries(MainServer + PUBLIC + Common + unofficial::mariadb-connector-cpp::mariadbcpp + OpenSSL::SSL + OpenSSL::Crypto + cryptopp::cryptopp + range-v3::range-v3 + Boost::json +) + +if (WIN32) + target_link_libraries(MainServer PUBLIC wsock32 ws2_32) +endif() + +if (MSVC) + target_compile_options(MainServer PUBLIC /W0) +else() + target_compile_options(MainServer PUBLIC -w) +endif() + +set_target_properties(MainServer PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR} +) + + +find_package(Python3 REQUIRED COMPONENTS Interpreter) + +add_custom_command( + TARGET MainServer + PRE_LINK + COMMAND ${CMAKE_COMMAND} -E echo "Running generate_command_includes.py..." + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_LIST_DIR}/generate_command_includes.py + WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR} +) + +if (MSVC) + target_compile_options(MainServer PUBLIC /W0) +else() + target_compile_options(MainServer PUBLIC -w) +endif() + +set_target_properties(MainServer PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR} +) diff --git a/MainServer/MainServer.vcxproj b/MainServer/MainServer.vcxproj deleted file mode 100644 index f9e945ac..00000000 --- a/MainServer/MainServer.vcxproj +++ /dev/null @@ -1,330 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {fb8cd300-b47a-498d-9038-7d0dcffe695f} - MainServer - 10.0 - - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - Application - true - v143 - Unicode - false - - - Application - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - $(SolutionDir)$(Platform)\ - - - ..\ExternalLibraries\vcpkg\installed - - - ..\ExternalLibraries\vcpkg\vcpkg_installed - - - ..\ExternalLibraries\vcpkg\vcpkg_installed - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - ..\Common\include; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries - - - Console - true - ..\ExternalLibraries\CommonLib\Common.lib - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - stdcpp20 - - - Console - true - true - true - - - - - Level3 - true - _DEBUG;_CONSOLE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) - true - stdcpplatest - ..\Common\include; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries - Disabled - Neither - Default - false - true - false - /bigobj - %(AdditionalOptions) - ProgramDatabase - - - Console - true - ..\ExternalLibraries\CommonLib\Common.lib - - - python "$(ProjectDir)generate_command_includes.py" - - - - - Level3 - false - true - NDEBUG;_CONSOLE;ENABLE_DEBUG_MESSAGES;_CRT_SECURE_NO_WARNINGS; - true - stdcpplatest - ..\Common\include; ..\ExternalLibraries\asio-1.24.0\include; ..\ExternalLibraries ; ..\ExternalLibraries\vcpkg\vcpkg_installed\x64-windows\include - MaxSpeed - Speed - AnySuitable - true - false - EditAndContinue - /bigobj - - - Console - false - false - false - ..\ExternalLibraries\CommonLib\Common.lib - Default - $(OutDir)$(TargetName)$(TargetExt) - ..\ExternalLibraries\vcpkg\vcpkg_installed\x64-windows\lib - - - python "$(ProjectDir)generate_command_includes.py" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MainServer/MainServer.vcxproj.filters b/MainServer/MainServer.vcxproj.filters deleted file mode 100644 index ef404b6b..00000000 --- a/MainServer/MainServer.vcxproj.filters +++ /dev/null @@ -1,154 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/MainServer/include/ChatCommands/Commands/ChangeRoomTitle.h b/MainServer/include/ChatCommands/Commands/ChangeRoomTitle.h index 231c393e..90170a9f 100644 --- a/MainServer/include/ChatCommands/Commands/ChangeRoomTitle.h +++ b/MainServer/include/ChatCommands/Commands/ChangeRoomTitle.h @@ -5,6 +5,7 @@ #include "../ChatCommands.h" #include "Utils/Utils.h" #include "../../MainServer.h" +#include namespace Main { diff --git a/MainServer/include/ChatCommands/Commands/ConvertRtToCoupons.h b/MainServer/include/ChatCommands/Commands/ConvertRtToCoupons.h new file mode 100644 index 00000000..1fc98b5e --- /dev/null +++ b/MainServer/include/ChatCommands/Commands/ConvertRtToCoupons.h @@ -0,0 +1,105 @@ +#ifndef CONVERT_RT_TO_COUPONS +#define CONVERT_RT_TO_COUPONS + + +#include "../ICommand.h" +#include "../ChatCommands.h" +#include "../../MainServer.h" + +namespace Main +{ + namespace Command + { + class Rt2Coupons final : public ICommand + { + private: + std::uint32_t m_rtToConvert{}; + + bool parseCommand(const std::string& providedCommand) override + { + std::smatch match; + if (std::regex_match(providedCommand, match, m_pattern)) + { + const std::string& matched_str = match[1].str(); + const std::from_chars_result result = std::from_chars(matched_str.data(), matched_str.data() + matched_str.size(), m_rtToConvert); + if (result.ec == std::errc()) + { + return true; + } + } + return false; + } + + public: + explicit Rt2Coupons(const Common::Enums::PlayerGrade requiredGrade) + : ICommand{ requiredGrade, "/rt2coupons (1 coupon = 5000 RT)", R"(^\S+\s(\d+)$)" } + { + } + + void execute(const std::string& command, std::shared_ptr session, MN::SessionsManager& sessionsManager, + MC::RoomsManager& roomsManager, MP::MainScheduler&, std::uint32_t roomNumber, + Main::MainServer& mainSv) override + { + if (!parseCommand(command)) + { + session->sendMessage("parsing error"); + return; + } + + auto& accountInfo = session->getAccountInfo(); + + constexpr std::uint32_t couponCost = 5'000; + constexpr std::uint32_t maxCoupons = 250; + + if (accountInfo.rockTotens < couponCost) + { + session->sendMessage("Error: you must have at least 5'000 RockTokens"); + return; + } + if (m_rtToConvert > accountInfo.rockTotens) + { + session->sendMessage("Error: you must specify an amount of RT that you currently have!"); + return; + } + + const std::uint32_t currentCoupons = session->getPlayer().getTotalCoupons(); + if (currentCoupons >= maxCoupons) + { + session->sendMessage("Error: You already have the maximum of 250 coupons."); + return; + } + + std::uint32_t couponsToSpawn = m_rtToConvert / couponCost; + if (couponsToSpawn == 0) + { + session->sendMessage("Error: you must convert at least 5'000 RockTokens (1 coupon)."); + return; + } + + if (currentCoupons + couponsToSpawn > maxCoupons) + { + couponsToSpawn = maxCoupons - currentCoupons; + session->sendMessage("Note: You can only hold " + std::to_string(maxCoupons) + " coupons. Conversion limited to " + std::to_string(couponsToSpawn) + "."); + } + + if (session->spawnCouponImmediate(couponsToSpawn)) + { + const std::uint32_t rtSpent = couponsToSpawn * couponCost; + session->setAccountRockTotens(accountInfo.rockTotens - rtSpent); + session->sendCurrency(); + session->sendMessage("Success: converted " + std::to_string(rtSpent) + " RT into " + std::to_string(couponsToSpawn) + " coupon(s)."); + } + else + { + session->sendMessage("Error: failed to spawn coupons - please report this issue"); + } + } + }; + + REGISTER_CMD(Rt2Coupons, Common::Enums::PlayerGrade::GRADE_NORMAL) + } +} + +#endif + + diff --git a/MainServer/include/ChatCommands/Commands/DebugRoom.h b/MainServer/include/ChatCommands/Commands/DebugRoom.h index c3aee5a2..79b0a408 100644 --- a/MainServer/include/ChatCommands/Commands/DebugRoom.h +++ b/MainServer/include/ChatCommands/Commands/DebugRoom.h @@ -133,16 +133,22 @@ namespace Main if (targetSession) { const auto& ainfo = targetSession->getAccountInfo(); - auto banInfoOpt = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getBanInfoByNickname, + auto banInfoOpt = scheduler.immediatePersist(std::source_location::current(), + &Main::Persistence::PersistentDatabase::getBanInfoByNickname, m_targetPlayerName); - auto muteInfoOpt = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getMuteInfoByNickname, + auto muteInfoOpt = scheduler.immediatePersist(std::source_location::current(), + &Main::Persistence::PersistentDatabase::getMuteInfoByNickname, m_targetPlayerName); - auto matchBannedOpt = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::hasBeenMatchBannedByNick, + auto matchBannedOpt = scheduler.immediatePersist(std::source_location::current(), + &Main::Persistence::PersistentDatabase::hasBeenMatchBannedByNick, m_targetPlayerName); - auto roomCreationDisabledUntilOpt = - scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getRoomCreationDisabledUntil, - m_targetPlayerName); - + auto roomCreationDisabledUntilOpt = scheduler.immediatePersist(std::source_location::current(), + &Main::Persistence::PersistentDatabase::getRoomCreationDisabledUntil, + m_targetPlayerName); + auto votekickDisabledUntilOpt = scheduler.immediatePersist(std::source_location::current(), + &Main::Persistence::PersistentDatabase::getVotekickDisabledUntil, + m_targetPlayerName); + session->sendMessage("User Information: " + m_targetPlayerName, Main::Enums::TIP); session->sendMessage(" - Player Status: ONLINE"); session->sendMessage(" - AccountID: " + std::to_string(ainfo.accountID)); @@ -152,6 +158,7 @@ namespace Main session->sendMessage(" - Level: " + std::to_string(ainfo.playerLevel)); session->sendMessage(" - MicroPoints: " + std::to_string(ainfo.microPoints)); session->sendMessage(" - RockTotens: " + std::to_string(ainfo.rockTotens)); + if (matchBannedOpt && *matchBannedOpt) { session->sendMessage("This player is cheat banned. Ignore all their requests.", Main::Enums::TIP); @@ -176,20 +183,28 @@ namespace Main session->sendMessage("This player cannot create rooms.", Main::Enums::TIP); session->sendMessage(" - Expiration (UTC): " + *roomCreationDisabledUntilOpt); } + if (votekickDisabledUntilOpt && *votekickDisabledUntilOpt != "0" && !votekickDisabledUntilOpt->empty()) + { + session->sendMessage("This player cannot start votekicks.", Main::Enums::TIP); + session->sendMessage(" - Expiration (UTC): " + *votekickDisabledUntilOpt); + } } else { // offline - auto ainfoOpt = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getPlayerInfoByNickname, + auto ainfoOpt = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getPlayerInfoByNickname, m_targetPlayerName); - auto banInfoOpt = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getBanInfoByNickname, + auto banInfoOpt = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getBanInfoByNickname, m_targetPlayerName); - auto muteInfoOpt = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getMuteInfoByNickname, + auto muteInfoOpt = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getMuteInfoByNickname, m_targetPlayerName); auto matchBannedOpt = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::hasBeenMatchBannedByNick, m_targetPlayerName); - auto roomCreationDisabledUntilOpt = + auto roomCreationDisabledUntilOpt = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getRoomCreationDisabledUntil, m_targetPlayerName); + auto votekickDisabledUntilOpt = + scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getVotekickDisabledUntil, + m_targetPlayerName); if (ainfoOpt) { @@ -226,6 +241,11 @@ namespace Main session->sendMessage("This player cannot create rooms.", Main::Enums::TIP); session->sendMessage(" - Expiration (UTC): " + *roomCreationDisabledUntilOpt); } + if (votekickDisabledUntilOpt && *votekickDisabledUntilOpt != "0" && !votekickDisabledUntilOpt->empty()) + { + session->sendMessage("This player cannot initiate votekicks.", Main::Enums::TIP); + session->sendMessage(" - Expiration (UTC): " + *votekickDisabledUntilOpt); + } } else { diff --git a/MainServer/include/ChatCommands/Commands/EnableVotekickFor.h b/MainServer/include/ChatCommands/Commands/EnableVotekickFor.h new file mode 100644 index 00000000..566acca0 --- /dev/null +++ b/MainServer/include/ChatCommands/Commands/EnableVotekickFor.h @@ -0,0 +1,153 @@ +#ifndef ENABLE_VOTEKICK_FOR_H +#define ENABLE_VOTEKICK_FOR_H + +#include "../ICommand.h" +#include "../ChatCommands.h" +#include "Utils/Utils.h" +#include "../../MainServer.h" +#include + +namespace Main +{ + namespace Command + { + class DisableVotekickFor final : public ICommand + { + private: + std::string m_targetPlayerName{}; + std::uint16_t m_durationDays{}; + + bool parseCommand(const std::string& providedCommand) override + { + std::smatch match; + if (std::regex_match(providedCommand, match, this->m_pattern)) + { + m_targetPlayerName = match[2].str(); + const std::string& durationStr = match[3].str(); + auto [ptr, ec] = std::from_chars(durationStr.data(), durationStr.data() + durationStr.size(), m_durationDays); + if (ec != std::errc{} || ptr != durationStr.data() + durationStr.size()) + { + return false; + } + return true; + } + return false; + } + + + public: + explicit DisableVotekickFor(const Common::Enums::PlayerGrade requiredGrade) + : ICommand{ requiredGrade, "/disablevotekickfor (in days) ", + R"(^(\S+)\s+(\S+)\s+(\d+)\s+(.*)$)" } + { + } + + void execute(const std::string& command, std::shared_ptr session, + MN::SessionsManager& sessionsManager, MC::RoomsManager&, MP::MainScheduler& scheduler, std::uint32_t, Main::MainServer&) override + { + if (!parseCommand(command)) + { + session->sendMessage("parsing error"); + return; + } + + auto targetSession = sessionsManager.findSessionByName(m_targetPlayerName.c_str()); + if (!targetSession) + { + using namespace std::chrono; + using namespace std::literals; + zoned_time zt{ "UTC", + local_seconds{duration_cast(system_clock::now().time_since_epoch()) + seconds(m_durationDays * 24 * 60 * 60)} + }; + const std::string votekickDisabledUntil = std::format("{:%Y-%m-%d %H:%M:%S}", zt.get_sys_time()); + + if (!scheduler.immediatePersist( + std::source_location::current(), + &Main::Persistence::PersistentDatabase::updateVotekickDisabledUntil, + m_targetPlayerName, + votekickDisabledUntil)) + { + session->sendMessage("error: player not found"); + return; + } + } + else + { + if (targetSession->getAccountInfo().playerGrade >= session->getAccountInfo().playerGrade) + { + session->sendMessage("Error: The target user has equal or greater grade than you."); + return; + } + if (!targetSession->disableVotekick(m_durationDays)) + { + session->sendMessage("error: unknown"); + return; + } + } + session->sendMessage("success"); + } + }; + + REGISTER_CMD(DisableVotekickFor, Common::Enums::PlayerGrade::GRADE_MOD) + + + + class EnableVotekickFor final : public ICommand + { + private: + std::string m_targetPlayerName{}; + + bool parseCommand(const std::string& providedCommand) override + { + std::smatch match; + if (std::regex_match(providedCommand, match, this->m_pattern)) + { + m_targetPlayerName = match[1].str(); + return true; + } + return false; + } + + public: + explicit EnableVotekickFor(const Common::Enums::PlayerGrade requiredGrade) + : ICommand{ requiredGrade, "/enablevotekickfor ", R"(^\S+\s+(.*)$)" } + { + } + + void execute(const std::string& command, std::shared_ptr session, + MN::SessionsManager& sessionsManager, MC::RoomsManager&, MP::MainScheduler& scheduler, std::uint32_t, Main::MainServer&) override + { + if (!parseCommand(command)) + { + session->sendMessage("parsing error"); + return; + } + + auto targetSession = sessionsManager.findSessionByName(m_targetPlayerName.c_str()); + if (!targetSession) + { + if (!scheduler.immediatePersist(std::source_location::current(), + &Main::Persistence::PersistentDatabase::resetVotekickDisabledUntil, m_targetPlayerName)) + { + session->sendMessage("error: the player was not found in the database"); + return; + } + } + else + { + if (!targetSession->enableVotekick()) + { + session->sendMessage("error: unknown"); + return; + } + } + session->sendMessage("success"); + } + }; + REGISTER_CMD(EnableVotekickFor, Common::Enums::PlayerGrade::GRADE_MOD) + + }; +} + + +#endif diff --git a/MainServer/include/ChatCommands/Commands/JoinInvisible.h b/MainServer/include/ChatCommands/Commands/JoinInvisible.h index fb3707bb..059bcb14 100644 --- a/MainServer/include/ChatCommands/Commands/JoinInvisible.h +++ b/MainServer/include/ChatCommands/Commands/JoinInvisible.h @@ -72,7 +72,7 @@ namespace Main } }; - REGISTER_CMD(Join, Common::Enums::PlayerGrade::GRADE_TESTER) + REGISTER_CMD(Join, Common::Enums::PlayerGrade::GRADE_MOD) diff --git a/MainServer/include/ChatCommands/Commands/Report.h b/MainServer/include/ChatCommands/Commands/Report.h new file mode 100644 index 00000000..910cd864 --- /dev/null +++ b/MainServer/include/ChatCommands/Commands/Report.h @@ -0,0 +1,140 @@ +#ifndef REPORT_COMPLEXCOMMAND_HEADER +#define REPORT_COMPLEXCOMMAND_HEADER + +#include "../ICommand.h" +#include "../ChatCommands.h" +#include "Utils/Utils.h" +#include "Utils/Constants.h" +#include "../../MainServer.h" +#include "../../Classes/ReportManager.h" +#include + +namespace Main +{ + namespace Command + { + class Report final : public ICommand + { + private: + std::string m_targetPlayername{}; + std::string m_reportReason{}; + + bool parseCommand(const std::string& providedCommand) override + { + std::smatch match; + if (std::regex_match(providedCommand, match, this->m_pattern)) + { + m_targetPlayername = match[2].str(); + m_reportReason = match[3].str(); + return true; + } + return false; + } + + public: + Report(const Common::Enums::PlayerGrade requiredGrade) + : ICommand{ requiredGrade, "/report " + , R"(^(\S+)\s*(\S+)\s*(.*)$)" } + { + } + + void execute(const std::string& command, std::shared_ptr session, MN::SessionsManager& sessionsManager, + MC::RoomsManager& roomsManager, + MP::MainScheduler& scheduler, std::uint32_t roomNumber, + Main::MainServer& mainServer) override + { + START_BENCHMARK + if (!parseCommand(command)) + { + session->sendMessage("error: parsing error, see /? for command usage"); + return; + } + + if (m_reportReason.empty()) + { + session->sendMessage("error: report reason cannot be empty"); + return; + } + + if (m_reportReason.size() >= Common::Constants::maxMailboxMessage) + { + session->sendMessage("error: report reason should be less than 255 characters"); + return; + } + + auto targetSession = sessionsManager.findSessionByName(m_targetPlayername.c_str()); + if (!targetSession) + { + session->sendMessage("error: target player not found or not online"); + return; + } + + const auto& reporterInfo = session->getAccountInfo(); + const auto& targetInfo = targetSession->getAccountInfo(); + + Common::Enums::PlayerGrade targetGrade = static_cast(targetInfo.playerGrade); + if (targetGrade == Common::Enums::PlayerGrade::GRADE_ES || + targetGrade == Common::Enums::PlayerGrade::GRADE_MOD || + targetGrade == Common::Enums::PlayerGrade::GRADE_TESTER || + targetGrade == Common::Enums::PlayerGrade::GRADE_GM) + { + session->sendMessage("error: you cannot report staff members or high-ranking players"); + return; + } + + std::uint32_t currentRoomNumber = session->getPlayer().getRoomNumber(); + std::string roomInfo = currentRoomNumber > 0 ? + "Room ID: " + std::to_string(currentRoomNumber) : + "Lobby"; + + Main::Structures::ReportInfo reportInfo( + 0, + reporterInfo.accountID, + targetInfo.accountID, + std::string(reporterInfo.nickname), + std::string(targetInfo.nickname), + m_reportReason, + currentRoomNumber + ); + + std::uint32_t reportId = mainServer.getReportManager().addReport(reportInfo); + + std::string reportMessage = "[REPORT #" + std::to_string(reportId) + "] " + + std::string(reporterInfo.nickname) + + " reported " + std::string(targetInfo.nickname) + + " in " + roomInfo + + ". Reason: " + m_reportReason; + + const auto& allSessions = sessionsManager.getAllSessions(); + std::uint32_t moderatorsNotified = 0; + + for (const auto& sessionPair : allSessions) + { + auto modSession = sessionPair.second; + const auto& modAccountInfo = modSession->getAccountInfo(); + + if (modAccountInfo.playerGrade == Common::Enums::PlayerGrade::GRADE_MOD || + modAccountInfo.playerGrade == Common::Enums::PlayerGrade::GRADE_TESTER || + modAccountInfo.playerGrade == Common::Enums::PlayerGrade::GRADE_GM) + { + modSession->sendMessage(reportMessage, Main::Enums::TIP); + modSession->sendMessage("Use /reports to see all reports or /rr " + + std::to_string(reportId) + " to acknowledge this report", Main::Enums::INFO); + moderatorsNotified++; + } + } + + session->sendMessage("Report submitted successfully (ID: " + std::to_string(reportId) + ")"); + + Utils::Logger::log("REPORT: " + std::string(reporterInfo.nickname) + " -> " + + std::string(targetInfo.nickname) + " | Room: " + roomInfo + + " | Reason: " + m_reportReason, Utils::LogType::Info, "ReportCommand"); + + END_BENCHMARK(Report::execute, session) + } + }; + + REGISTER_CMD(Report, Common::Enums::PlayerGrade::GRADE_NORMAL) + } +} +#endif \ No newline at end of file diff --git a/MainServer/include/ChatCommands/Commands/ReportReceived.h b/MainServer/include/ChatCommands/Commands/ReportReceived.h new file mode 100644 index 00000000..8b4ba6c3 --- /dev/null +++ b/MainServer/include/ChatCommands/Commands/ReportReceived.h @@ -0,0 +1,114 @@ +#ifndef REPORTRECEIVED_COMPLEXCOMMAND_HEADER +#define REPORTRECEIVED_COMPLEXCOMMAND_HEADER + +#include "../ICommand.h" +#include "../ChatCommands.h" +#include "Utils/Utils.h" +#include "Utils/Constants.h" +#include "../../MainServer.h" +#include "../../Classes/ReportManager.h" +#include "../../Structures/Mailbox.h" +#include + +namespace Main +{ + namespace Command + { + class ReportReceived final : public ICommand + { + private: + std::uint32_t m_reportId{}; + + bool parseCommand(const std::string& providedCommand) override + { + std::smatch match; + if (std::regex_match(providedCommand, match, this->m_pattern)) + { + const std::string& reportIdStr = match[2].str(); + auto [ptr, ec] = std::from_chars(reportIdStr.data(), reportIdStr.data() + reportIdStr.size(), m_reportId); + if (ec != std::errc{} || ptr != reportIdStr.data() + reportIdStr.size()) + { + return false; + } + return true; + } + return false; + } + + public: + ReportReceived(const Common::Enums::PlayerGrade requiredGrade) + : ICommand{ requiredGrade, "/rr - Acknowledge a report and thank the reporter" + , R"(^(\S+)\s*(\d+)\s*(.*)$)" } + { + } + + void execute(const std::string& command, std::shared_ptr session, MN::SessionsManager& sessionsManager, + MC::RoomsManager& roomsManager, + MP::MainScheduler& scheduler, std::uint32_t roomNumber, Main::MainServer& mainServer) override + { + START_BENCHMARK + if (!parseCommand(command)) + { + session->sendMessage("error: parsing error, see /? for command usage. Usage: /rr "); + return; + } + + Main::Structures::ReportInfo report; + if (!mainServer.getReportManager().getReportById(m_reportId, report)) + { + session->sendMessage("error: report with ID " + std::to_string(m_reportId) + " not found"); + return; + } + + if (report.isAcknowledged) + { + session->sendMessage("Report #" + std::to_string(m_reportId) + " was already acknowledged by " + + report.acknowledgedBy + " at " + std::string(std::ctime(&report.acknowledgedTime))); + return; + } + + const auto& modInfo = session->getAccountInfo(); + + auto reporterSession = sessionsManager.findSessionByName(report.reporterNickname.c_str()); + + if (reporterSession) + { + std::string thankYouMessage = "Hello " + report.reporterNickname + + ", Thank you for reporting a player for breaking our rules. We will take appropriate action based on our findings."; + + reporterSession->sendMessage(thankYouMessage, Main::Enums::TIP); + session->sendMessage("Thank you message sent to " + report.reporterNickname); + } + else + { + Main::Structures::Mailbox mailbox; + mailbox.accountId = report.reporterAccountId; + mailbox.timestamp = std::time(nullptr); + mailbox.hasBeenRead = 0; + std::strncpy(mailbox.nickname, modInfo.nickname, sizeof(mailbox.nickname) - 1); + mailbox.nickname[sizeof(mailbox.nickname) - 1] = '\0'; + + std::string mailboxMessage = "Hello " + report.reporterNickname + + ", Thank you for reporting a player for breaking our rules. We will take appropriate action based on our findings."; + + std::strncpy(mailbox.message, mailboxMessage.c_str(), sizeof(mailbox.message) - 1); + mailbox.message[sizeof(mailbox.message) - 1] = '\0'; + + session->sendOfflineMailbox(mailbox); + session->sendMessage("Thank you message sent via mailbox to " + report.reporterNickname + " (offline)"); + } + + mainServer.getReportManager().acknowledgeReport(m_reportId, std::string(modInfo.nickname)); + + Utils::Logger::log("REPORT #" + std::to_string(m_reportId) + " ACKNOWLEDGED: " + std::string(modInfo.nickname) + + " acknowledged report from " + report.reporterNickname, + Utils::LogType::Info, "ReportReceivedCommand"); + + END_BENCHMARK(ReportReceived::execute, session) + } + }; + + REGISTER_CMD(ReportReceived, Common::Enums::PlayerGrade::GRADE_MOD) + } +} +#endif \ No newline at end of file diff --git a/MainServer/include/ChatCommands/Commands/Reports.h b/MainServer/include/ChatCommands/Commands/Reports.h new file mode 100644 index 00000000..f4a03ed3 --- /dev/null +++ b/MainServer/include/ChatCommands/Commands/Reports.h @@ -0,0 +1,82 @@ +#ifndef REPORTS_COMPLEXCOMMAND_HEADER +#define REPORTS_COMPLEXCOMMAND_HEADER + +#include "../ICommand.h" +#include "../ChatCommands.h" +#include "Utils/Utils.h" +#include "Utils/Constants.h" +#include "../../MainServer.h" +#include "../../Classes/ReportManager.h" +#include + +namespace Main +{ + namespace Command + { + class Reports final : public ICommand + { + public: + Reports(const Common::Enums::PlayerGrade requiredGrade) + : ICommand{ requiredGrade, "/reports - View all pending reports" } + { + } + + void execute(const std::string& command, std::shared_ptr session, MN::SessionsManager& sessionsManager, + MC::RoomsManager& roomsManager, + MP::MainScheduler& scheduler, std::uint32_t roomNumber, Main::MainServer& mainServer) override + { + START_BENCHMARK + + auto unacknowledgedReports = mainServer.getReportManager().getUnacknowledgedReports(); + auto totalReports = mainServer.getReportManager().getTotalReportsCount(); + auto unacknowledgedCount = mainServer.getReportManager().getUnacknowledgedReportsCount(); + + if (unacknowledgedReports.empty()) + { + session->sendMessage("No pending reports. Total reports in system: " + std::to_string(totalReports)); + END_BENCHMARK(Reports::execute, session) + return; + } + + session->sendMessage("=== PENDING REPORTS (" + std::to_string(unacknowledgedCount) + "/" + std::to_string(totalReports) + ") ===", Main::Enums::TIP); + + for (const auto& report : unacknowledgedReports) + { + std::string roomInfo = report.roomNumber > 0 ? + "Room ID: " + std::to_string(report.roomNumber) : + "Lobby"; + + std::string timeAgo = "just now"; + std::time_t now = std::time(nullptr); + std::time_t diff = now - report.timestamp; + + if (diff > 60) + { + std::time_t minutes = diff / 60; + if (minutes > 60) + { + std::time_t hours = minutes / 60; + timeAgo = std::to_string(hours) + "h ago"; + } + else + { + timeAgo = std::to_string(minutes) + "m ago"; + } + } + + session->sendMessage("[" + std::to_string(report.reportId) + "] " + + report.reporterNickname + " -> " + report.reportedNickname + + " (" + roomInfo + ") - " + timeAgo, Main::Enums::INFO); + session->sendMessage(" Reason: " + report.reason, Main::Enums::INFO); + session->sendMessage(" Use /rr " + std::to_string(report.reportId) + + " to acknowledge this report", Main::Enums::TIP); + } + + END_BENCHMARK(Reports::execute, session) + } + }; + + REGISTER_CMD(Reports, Common::Enums::PlayerGrade::GRADE_MOD) + } +} +#endif \ No newline at end of file diff --git a/MainServer/include/ChatCommands/Commands/SendRewards.h b/MainServer/include/ChatCommands/Commands/SendRewards.h new file mode 100644 index 00000000..518f5c47 --- /dev/null +++ b/MainServer/include/ChatCommands/Commands/SendRewards.h @@ -0,0 +1,90 @@ +#ifndef SENDREWARDS_COMPLEXCOMMAND_H +#define SENDREWARDS_COMPLEXCOMMAND_H + +#include "../ICommand.h" +#include "../ChatCommands.h" +#include "Utils/Utils.h" +#include "Utils/Constants.h" +#include "../../MainServer.h" +#include + +namespace Main +{ + namespace Command + { + class SendRewards final : public ICommand + { + private: + std::uint32_t m_itemId{}; + std::string m_giftDescription{}; + + bool parseCommand(const std::string& providedCommand) override + { + std::smatch match; + if (std::regex_match(providedCommand, match, this->m_pattern)) + { + const std::string& itemid = match[2].str(); + auto [ptr, ec] = std::from_chars(itemid.data(), itemid.data() + itemid.size(), m_itemId); + if (ec != std::errc{} || ptr != itemid.data() + itemid.size()) + { + return false; + } + + m_giftDescription = match[3].str(); + return true; + } + return false; + } + + public: + SendRewards(const Common::Enums::PlayerGrade requiredGrade) + : ICommand{ requiredGrade, "/sendrewards ", R"(^(\S+)\s*(\d+)\s*(.*)$)" } + { + } + + void execute(const std::string& command, std::shared_ptr session, MN::SessionsManager& sessionsManager, MC::RoomsManager& roomsManager, + MP::MainScheduler& scheduler, std::uint32_t roomNum, Main::MainServer&) override + { + if (!parseCommand(command)) + { + session->sendMessage("error: parsing error, see /? for command usage"); + return; + } + if (!Main::CdbUtils::itemExists(m_itemId)) + { + session->sendMessage("error: ItemID not found"); + return; + } + if (m_giftDescription.size() >= Common::Constants::maxMailboxMessage) + { + session->sendMessage("error: description should be less than 255 characters"); + return; + } + + if (Main::Classes::Room* room = roomsManager.getRoomByNumber(roomNum)) + { + for (auto& [u, targetSession] : room->getAllPlayersWithSessions()) + { + if (session->getAccountId() == targetSession->getAccountId()) continue; + + if (targetSession->receiveGift(m_itemId, m_giftDescription)) + { + session->sendMessage(" - " + std::string{ targetSession->getAccountInfo().nickname } + ": success", Main::Enums::TIP); + } + else + { + session->sendMessage(" - " + std::string{ targetSession->getAccountInfo().nickname } + ": ERROR (not enough space)"); + } + } + } + else + { + session->sendMessage("error: you must be in a room"); + } + } + }; + + REGISTER_CMD(SendRewards, Common::Enums::PlayerGrade::GRADE_TESTER) + } +} +#endif diff --git a/MainServer/include/ChatCommands/Commands/SetCurrency.h b/MainServer/include/ChatCommands/Commands/SetCurrency.h index b2d42e07..fa575415 100644 --- a/MainServer/include/ChatCommands/Commands/SetCurrency.h +++ b/MainServer/include/ChatCommands/Commands/SetCurrency.h @@ -26,7 +26,7 @@ namespace Main } }; - REGISTER_CMD(SetCurrency, Common::Enums::PlayerGrade::GRADE_NORMAL) + REGISTER_CMD(SetCurrency, Common::Enums::PlayerGrade::GRADE_TESTER) } } diff --git a/MainServer/include/ChatCommands/Commands/SetNickname.h b/MainServer/include/ChatCommands/Commands/SetNickname.h index 3cfb963a..07ae19ba 100644 --- a/MainServer/include/ChatCommands/Commands/SetNickname.h +++ b/MainServer/include/ChatCommands/Commands/SetNickname.h @@ -5,6 +5,9 @@ #include "../ICommand.h" #include "Utils/Utils.h" #include "../../MainServer.h" +#include +#include "../../../../Common/include/Network/Packet.h" +#include "../../../../Common/include/Enums/MiscellaneousEnums.h" namespace Main { @@ -20,7 +23,7 @@ namespace Main std::smatch match; if (std::regex_match(providedCommand, match, this->m_pattern)) { - m_nickname = match[1].str(); + m_nickname = match[1].str(); return true; } return false; @@ -32,17 +35,32 @@ namespace Main { } - void execute(const std::string& command, std::shared_ptr session, MN::SessionsManager&, MC::RoomsManager&, MP::MainScheduler&, + void execute(const std::string& command, std::shared_ptr session, MN::SessionsManager&, MC::RoomsManager&, MP::MainScheduler&, std::uint32_t, - Main::MainServer&) override + Main::MainServer&) override { if (!parseCommand(command)) { session->sendMessage("parsing error"); return; } - session->setPlayerName(m_nickname.c_str()); - session->sendMessage("success (relog)"); + + if (!session->setPlayerName()) return; + + struct SetName { + std::uint32_t unknown0; + std::uint32_t unknown1; + char newNick[16]{}; + }; + + SetName nicknameData{ 0, 0 }; + std::memcpy(nicknameData.newNick, m_nickname.c_str(), m_nickname.length()); + + Common::Network::Packet nicknamePacket; + nicknamePacket.setTcpHeader(session->getId(), Common::Enums::NO_ENCRYPTION); + nicknamePacket.setCommand(102, 1, 53, 0); + nicknamePacket.setData(reinterpret_cast(&nicknameData), sizeof(nicknameData)); + session->asyncWrite(nicknamePacket); } }; @@ -50,4 +68,4 @@ namespace Main } } -#endif \ No newline at end of file +#endif diff --git a/MainServer/include/ChatCommands/Commands/StaffCommand.h b/MainServer/include/ChatCommands/Commands/StaffCommand.h new file mode 100644 index 00000000..409da038 --- /dev/null +++ b/MainServer/include/ChatCommands/Commands/StaffCommand.h @@ -0,0 +1,96 @@ +#ifndef COMMAND_STAFF_H +#define COMMAND_STAFF_H + +#include "../ChatCommands.h" +#include "../ICommand.h" +#include "Utils/Utils.h" +#include "../../MainServer.h" + +namespace Main +{ + namespace Command + { + class Staff final : public ICommand + { + public: + explicit Staff(const Common::Enums::PlayerGrade requiredGrade) + : ICommand{ requiredGrade, "/staff - Toggles Staff prefix in nickname", "^\\S+$" } + { + } + + void execute(const std::string& command, std::shared_ptr session, MN::SessionsManager& sessionsManager, MC::RoomsManager& roomsManager, MP::MainScheduler& scheduler, + std::uint32_t roomNumber, + Main::MainServer& mainServer) override + { + if (session->getPlayer().getRoomNumber() != 0) + { + session->sendMessage("You cannot use this command while in a room."); + return; + } + + const auto& accountInfo = session->getAccountInfo(); + std::string currentNickname = accountInfo.nickname; + + std::string prefix; + if (accountInfo.playerGrade == Common::Enums::PlayerGrade::GRADE_MOD) + { + prefix = "[MOD]"; + } + else if (accountInfo.playerGrade == Common::Enums::PlayerGrade::GRADE_ES) + { + prefix = "[ES]"; + } + else if (accountInfo.playerGrade == Common::Enums::PlayerGrade::GRADE_GM || accountInfo.playerGrade == Common::Enums::PlayerGrade::GRADE_TESTER) + { + prefix = "[GM]"; + } + + std::string newNickname; + + if (currentNickname.find(prefix) == 0) + { + newNickname = currentNickname.substr(prefix.length()); + size_t start = newNickname.find_first_not_of(" \t"); + if (start != std::string::npos) + { + newNickname = newNickname.substr(start); + } + else + { + newNickname = ""; + } + } + else + { + newNickname = prefix + currentNickname; + } + + if (newNickname.length() >= 16) + { + session->sendMessage("New nickname would be too long."); + return; + } + + struct Staff { + std::uint32_t unknown0; + std::uint32_t unknown1; + char newNick[16]{}; + }; + Staff nicknameData{ 0, 0 }; + std::memcpy(nicknameData.newNick, newNickname.c_str(), newNickname.length()); + Common::Network::Packet nicknamePacket; + nicknamePacket.setTcpHeader(session->getId(), Common::Enums::NO_ENCRYPTION); + nicknamePacket.setCommand(102, 1, 53, 0); + nicknamePacket.setData(reinterpret_cast(&nicknameData), sizeof(nicknameData)); + session->asyncWrite(nicknamePacket); + session->setPlayerName(newNickname.c_str()); + + session->sendMessage("Staff prefix toggled."); + } + }; + + REGISTER_CMD(Staff, Common::Enums::PlayerGrade::GRADE_ES); + } +} + +#endif \ No newline at end of file diff --git a/MainServer/include/ChatCommands/ICommand.h b/MainServer/include/ChatCommands/ICommand.h index 36ec8690..7c130b75 100644 --- a/MainServer/include/ChatCommands/ICommand.h +++ b/MainServer/include/ChatCommands/ICommand.h @@ -1,14 +1,15 @@ -#ifndef INTERFACE_COMMAND_H -#define INTERFACE_COMMAND_H +#ifndef INTERFACE_COMMAND_HEADER +#define INTERFACE_COMMAND_HEADER #include -#include #include "Network/Packet.h" #include "../Network/MainSession.h" #include "../Network/MainSessionManager.h" #include "Enums/PlayerEnums.h" #include "Utils/Utils.h" +#include + namespace Main { class MainServer; @@ -34,18 +35,17 @@ namespace Main class ICommand { protected: - std::regex m_pattern{}; + std::regex m_pattern; Common::Enums::PlayerGrade m_requiredGrade{}; std::string m_commandDescription{}; explicit ICommand(Common::Enums::PlayerGrade requiredGrade, const std::string& commandDescription, const std::string& regexPattern = "") : m_requiredGrade{ requiredGrade } , m_commandDescription{ commandDescription } - , m_pattern { regexPattern } + , m_pattern{ regexPattern } { } - ICommand(const ICommand&) = delete; - ICommand() = delete; + private: virtual bool parseCommand(const std::string& providedCommand) { return true; }; @@ -68,4 +68,4 @@ namespace Main }; } } -#endif \ No newline at end of file +#endif diff --git a/MainServer/include/Classes/ClanRoom.h b/MainServer/include/Classes/ClanRoom.h index 46cbc35b..aeaac73a 100644 --- a/MainServer/include/Classes/ClanRoom.h +++ b/MainServer/include/Classes/ClanRoom.h @@ -12,6 +12,7 @@ #include "../Structures/Room/ClientRoomCreationInfo.h" #include #include +#include namespace Main { diff --git a/MainServer/include/Classes/Player.h b/MainServer/include/Classes/Player.h index cbef2824..ca354bb3 100644 --- a/MainServer/include/Classes/Player.h +++ b/MainServer/include/Classes/Player.h @@ -36,7 +36,6 @@ namespace Main using Friend = Main::Structures::Friend; using Mailbox = Main::Structures::Mailbox; using Giftbox = Main::Structures::Giftbox; - using Session = Main::Network::Session; using TradedItem = Main::Structures::TradeBasicItem; AccountInfo m_accountInfo{}; @@ -49,6 +48,7 @@ namespace Main std::uint16_t m_ping{}; bool m_isMuted{ false }; bool m_isRoomCreationEnabled{ true }; + bool m_isVotekickEnabled{true}; std::string m_mutedBy{}; std::string m_muteReason{}; std::string m_mutedUntil{}; @@ -105,6 +105,9 @@ namespace Main void disableRoomCreation(); void enableRoomCreation(); bool isRoomCreationEnabled() const noexcept; + void disableVotekick(); + void enableVotekick(); + bool isVotekickEnabled() const noexcept; Main::Structures::MuteInfo getMuteInfo() const; bool isMuted() const; void resetKillDeath(); @@ -125,11 +128,16 @@ namespace Main /********* Player items related /*********/ void setUnequippedItems(const std::vector& items); + bool isItemTradeable(const Main::Structures::ItemSerialInfo& itemSerialInfo) const; std::optional findItemIdBySerialInfo(const Main::Structures::ItemSerialInfo& itemSerialInfo) const; + std::optional getBossBattleTicket() const; + std::optional> + findItemIdAndDurabilityBySerialInfo(const Main::Structures::ItemSerialInfo& itemSerialInfo) const; std::optional findMaxItemNumber() const; bool prolongItem(const Main::Structures::ItemSerialInfo& newItemSerialInfo); const std::array& getEquippedItems() const; std::vector getEquippedItemsFor(std::uint16_t characterID) const; + std::vector getUnlimitedEquippedWeaponsFor(std::uint16_t characterID) const; const std::unordered_map& getItems() const; const std::vector getItemsAsVec() const; bool deleteItemBasic(const Main::Structures::ItemSerialInfo& itemSerialInfo); @@ -140,6 +148,10 @@ namespace Main void setEquippedItems(const std::unordered_map>& equippedItems); std::optional> addEnergyToItem(const Main::Structures::ItemSerialInfo& itemSerialInfo, std::uint32_t energyAdded); + std::vector reduceEquippedItemsDurabilities(std::size_t characterID, std::uint32_t weaponRestriction); + bool updateItemDurabilityByNumber(std::uint32_t itemNumber, std::uint32_t newDurability); + std::optional getItemEnergy(const Main::Structures::ItemSerialInfo& itemSerialInfo) const; + // ugly design but easier to write, ideally we shouldn't pass the scheduler to this function... void equipItem(const std::uint16_t itemNumber, Main::Persistence::MainScheduler& scheduler, std::uint32_t character = -1); std::optional unequipItem(uint64_t itemType, Main::Persistence::MainScheduler& scheduler); diff --git a/MainServer/include/Classes/ReportManager.h b/MainServer/include/Classes/ReportManager.h new file mode 100644 index 00000000..babcdd4b --- /dev/null +++ b/MainServer/include/Classes/ReportManager.h @@ -0,0 +1,118 @@ +#ifndef REPORT_MANAGER_H +#define REPORT_MANAGER_H + +#include "../Structures/ReportInfo.h" +#include +#include +#include + +namespace Main +{ + namespace Classes + { + class ReportManager + { + private: + std::vector m_reports; + std::uint32_t m_nextReportId{ 1 }; + std::mutex m_mutex; + + public: + ReportManager() = default; + + std::uint32_t addReport(const Main::Structures::ReportInfo& report) + { + std::lock_guard lock(m_mutex); + auto newReport = report; + newReport.reportId = m_nextReportId++; + newReport.timestamp = std::time(nullptr); + m_reports.push_back(newReport); + return newReport.reportId; + } + + std::vector getAllReports() + { + std::lock_guard lock(m_mutex); + return m_reports; + } + + std::vector getUnacknowledgedReports() + { + std::lock_guard lock(m_mutex); + std::vector unacknowledged; + for (const auto& report : m_reports) + { + if (!report.isAcknowledged) + { + unacknowledged.push_back(report); + } + } + return unacknowledged; + } + + bool acknowledgeReport(std::uint32_t reportId, const std::string& acknowledgedBy) + { + std::lock_guard lock(m_mutex); + for (auto& report : m_reports) + { + if (report.reportId == reportId) + { + report.isAcknowledged = true; + report.acknowledgedBy = acknowledgedBy; + report.acknowledgedTime = std::time(nullptr); + return true; + } + } + return false; + } + + bool getReportById(std::uint32_t reportId, Main::Structures::ReportInfo& outReport) + { + std::lock_guard lock(m_mutex); + for (const auto& report : m_reports) + { + if (report.reportId == reportId) + { + outReport = report; + return true; + } + } + return false; + } + + std::size_t getTotalReportsCount() + { + std::lock_guard lock(m_mutex); + return m_reports.size(); + } + + std::size_t getUnacknowledgedReportsCount() + { + std::lock_guard lock(m_mutex); + std::size_t count = 0; + for (const auto& report : m_reports) + { + if (!report.isAcknowledged) + { + count++; + } + } + return count; + } + + void cleanOldReports() + { + std::lock_guard lock(m_mutex); + std::time_t weekAgo = std::time(nullptr) - (7 * 24 * 60 * 60); + m_reports.erase( + std::remove_if(m_reports.begin(), m_reports.end(), + [weekAgo](const Main::Structures::ReportInfo& report) { + return report.timestamp < weekAgo; + }), + m_reports.end()); + } + }; + } +} + +#endif \ No newline at end of file diff --git a/MainServer/include/Classes/Room.h b/MainServer/include/Classes/Room.h index 3708068c..bfd4f033 100644 --- a/MainServer/include/Classes/Room.h +++ b/MainServer/include/Classes/Room.h @@ -15,6 +15,7 @@ #include "../Network/MainSession.h" #include "../Structures/Room/RoomPlayerInfo.h" #include "../../include/Structures/EndScoreboard.h" +#include namespace Main { @@ -175,6 +176,29 @@ namespace Main return ret; } + template + bool checkWeapons(Predicate&& pred, const std::string& failMessage) const + { + if (m_players.empty()) return false; + + auto& pair = m_players[0]; + auto hostSession = pair.second.lock(); + if (!hostSession) return false; + + bool ret = true; + for (auto& [roomInfo, session] : m_players) + { + if (auto actSession = session.lock(); + actSession && roomInfo.state == Common::Enums::STATE_READY && !pred(actSession)) + { + hostSession->sendMessage( + "(error) Player " + std::string{ actSession->getAccountInfo().nickname } + " " + failMessage + ); + ret = false; + } + } + return ret; + } public: @@ -232,6 +256,7 @@ namespace Main bool changePlayerTeam(const Main::Structures::UniqueId& uniqueId, std::uint32_t newTeam); std::optional getTeamForSession(std::uint32_t sessionId) const; bool isEveryoneCsd() const; + bool isEveryoneBasic() const; void muteRoom(); void unmuteRoom(); diff --git a/MainServer/include/Detail/CdbUtils.h b/MainServer/include/Detail/CdbUtils.h index dfb83834..8b61b4b6 100644 --- a/MainServer/include/Detail/CdbUtils.h +++ b/MainServer/include/Detail/CdbUtils.h @@ -305,6 +305,25 @@ namespace Main } return std::nullopt; } + + inline std::vector getAllRareCapsuleItems() + { + std::vector rareItems; + const auto& capsuleInfos = cdbCapsuleInfos::getInstance().getEntries(); + + for (const auto& [unused, capsuleInfo] : capsuleInfos) + { + if (auto relatedPackages = cdbCapsulePackageInfos::getInstance().getEntriesFor(capsuleInfo.gi_infoid)) + { + for (const auto& packageInfo : relatedPackages.value()) + { + if (packageInfo.gi_type == 1) + rareItems.push_back(packageInfo); + } + } + } + return rareItems; + } } } diff --git a/MainServer/include/Detail/Utilities.h b/MainServer/include/Detail/Utilities.h index 646be063..567cdcca 100644 --- a/MainServer/include/Detail/Utilities.h +++ b/MainServer/include/Detail/Utilities.h @@ -12,6 +12,8 @@ #include #include #include "../Common/include/Enums/RoomEnums.h" +#include +#include namespace Main { @@ -26,9 +28,9 @@ namespace Main template - requires (std::is_same_v || + requires (std::is_same_v || std::is_same_v) - T parseDataImpl(const PacketType& request, std::uint32_t offset = 0, std::source_location location = std::source_location::current()) + T parseDataImpl(const PacketType& request, std::uint32_t offset = 0, std::source_location location = std::source_location::current()) { static_assert(std::is_trivially_copyable_v, "Details::parseData requires T to be trivially copyable"); @@ -224,8 +226,31 @@ namespace Main else return false; } + inline bool isBasicItem(Common::Enums::ItemType itemType, std::uint32_t itemId) + { + using ItemType = Common::Enums::ItemType; + static const std::unordered_map> basicItems = { + { ItemType::MELEE, { 3010100 } }, + { ItemType::RIFLE, { 3020000 } }, + { ItemType::SHOTGUN, { 3030000 } }, + { ItemType::SNIPER, { 3040000 } }, + { ItemType::MG, { 3050000 } }, + { ItemType::BAZOOKA, { 3060000, 3060001 } }, + { ItemType::GRENADE, { 3070000 } } + }; + + if (itemType == ItemType::ACC_UPPER) + return true; + + if (auto it = basicItems.find(itemType); it != basicItems.end()) + return it->second.contains(itemId); + + return false; + } + + void broadcastPlayerItems(Main::Classes::RoomsManager& roomsManager, std::shared_ptr session, const Common::Network::Packet& request); } } -#endif \ No newline at end of file +#endif diff --git a/MainServer/include/Handlers/CapsuleSpinHandler.h b/MainServer/include/Handlers/CapsuleSpinHandler.h index 041983ec..6aaeadc4 100644 --- a/MainServer/include/Handlers/CapsuleSpinHandler.h +++ b/MainServer/include/Handlers/CapsuleSpinHandler.h @@ -12,11 +12,20 @@ namespace Main { namespace Handlers { - inline std::optional> itemSelectionAlgorithm(std::uint32_t gi_infoid, std::uint32_t gi_price, std::uint32_t gi_type) + inline std::optional> itemSelectionAlgorithm(std::uint32_t gi_infoid, std::uint32_t gi_price, std::uint32_t gi_type, + bool isLuckySpin) { static const std::array averageSpinCostByCurrency = Main::CdbUtils::getAverageSpinCostByCurrency(); - double averageSpinCost = 0.0; + if (isLuckySpin) + { + static std::mt19937 rng(std::random_device{}()); + std::uniform_int_distribution dist(0, 1); + const std::uint32_t selected = (dist(rng) == 0) ? 5336503 : 5336504; // silver / gold lucky box + return std::pair{ selected, 0 }; + } + + double averageSpinCost = 0.0; if (gi_type < averageSpinCostByCurrency.size()) { averageSpinCost = averageSpinCostByCurrency[gi_type]; @@ -62,26 +71,49 @@ namespace Main } - inline void removeCurrencyByCapsuleType(std::shared_ptr session, const Main::Structures::AccountInfo& accountInfo, - Main::Enums::CapsuleCurrencyType capsuleCurrencyType, std::uint32_t toRemove) + inline void removeCurrencyByCapsuleType( + const std::shared_ptr& session, + const Main::Structures::AccountInfo& accountInfo, + Main::Enums::CapsuleCurrencyType capsuleCurrencyType, + std::uint32_t toRemove, + const Main::Structures::CapsuleListDatabase& capsuleSaleEvent) { - if (capsuleCurrencyType == Main::Enums::CapsuleCurrencyType::CAPSULE_COINS) - { - session->setAccountCoins(accountInfo.coins - toRemove); - } - else if (capsuleCurrencyType == Main::Enums::CapsuleCurrencyType::CAPSULE_ROCKTOTENS) - { - session->setAccountRockTotens(accountInfo.rockTotens - toRemove); - } - else + auto safeSubtract = [](std::uint32_t balance, std::uint32_t remove) { + return (remove >= balance) ? 0u : balance - remove; + }; + + const std::uint32_t now = static_cast( + std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()) + ); + const bool inSalePeriod = (now >= capsuleSaleEvent.saleEventStartDate && + now <= capsuleSaleEvent.saleEventEndDate); + + switch (capsuleCurrencyType) { - session->setAccountMicroPoints(accountInfo.microPoints - toRemove); + case Main::Enums::CapsuleCurrencyType::CAPSULE_COINS: + session->setAccountCoins(safeSubtract(accountInfo.coins, toRemove)); + break; + + case Main::Enums::CapsuleCurrencyType::CAPSULE_ROCKTOTENS: + if (inSalePeriod) + toRemove = capsuleSaleEvent.newRtPrice; + session->setAccountRockTotens(safeSubtract(accountInfo.rockTotens, toRemove)); + break; + + case Main::Enums::CapsuleCurrencyType::CAPSULE_MICROPOINTS: + default: + if (inSalePeriod) + toRemove = capsuleSaleEvent.newMpPrice; + session->setAccountMicroPoints(safeSubtract(accountInfo.microPoints, toRemove)); + break; } } + inline void handleCapsuleSpin(const Common::Network::Packet& request, std::shared_ptr session, Main::Network::SessionsManager& sessionsManager, - const Main::ClientData::CapsuleSpin& capsuleSpinData) + const Main::ClientData::CapsuleSpin& capsuleSpinData, + const Main::Structures::CapsuleListDatabase& capsuleSaleEvent) { START_BENCHMARK @@ -94,9 +126,45 @@ namespace Main const auto& accountInfo = session->getAccountInfo(); const auto totalSpins = request.getOption(); - if (capsuleInfo->gi_type == Main::Enums::CapsuleCurrencyType::CAPSULE_COINS && accountInfo.coins < capsuleInfo->gi_price * totalSpins - || capsuleInfo->gi_type == Main::Enums::CapsuleCurrencyType::CAPSULE_ROCKTOTENS && accountInfo.rockTotens < capsuleInfo->gi_price * totalSpins - || capsuleInfo->gi_type == Main::Enums::CapsuleCurrencyType::CAPSULE_MICROPOINTS && accountInfo.microPoints < capsuleInfo->gi_price * totalSpins) + // check if enough currency + sales + const std::uint32_t now = static_cast(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); + const bool inSalePeriod = (now >= capsuleSaleEvent.saleEventStartDate && now <= capsuleSaleEvent.saleEventEndDate); + std::uint32_t pricePerSpin = capsuleInfo->gi_price; + + if (inSalePeriod) + { + switch (capsuleInfo->gi_type) + { + case Main::Enums::CapsuleCurrencyType::CAPSULE_ROCKTOTENS: + pricePerSpin = capsuleSaleEvent.newRtPrice; + break; + case Main::Enums::CapsuleCurrencyType::CAPSULE_MICROPOINTS: + pricePerSpin = capsuleSaleEvent.newMpPrice; + break; + default: + break; + } + } + + const std::uint32_t totalPrice = pricePerSpin * totalSpins; + bool notEnoughCurrency = false; + + switch (capsuleInfo->gi_type) + { + case Main::Enums::CapsuleCurrencyType::CAPSULE_COINS: + notEnoughCurrency = accountInfo.coins < totalPrice; + break; + case Main::Enums::CapsuleCurrencyType::CAPSULE_ROCKTOTENS: + notEnoughCurrency = accountInfo.rockTotens < totalPrice; + break; + case Main::Enums::CapsuleCurrencyType::CAPSULE_MICROPOINTS: + notEnoughCurrency = accountInfo.microPoints < totalPrice; + break; + default: + break; + } + + if (notEnoughCurrency) { response.setExtra(Main::Enums::CapsuleSpinExtra::CAPSULE_SPIN_NOT_ENOUGH_CURRENCY); response.setOption(capsuleInfo->gi_type); @@ -136,8 +204,8 @@ namespace Main } lastMission = static_cast(response.getMission()); - const auto wonItemIdAndType = itemSelectionAlgorithm(capsuleInfo->gi_infoid, capsuleInfo->gi_price, - capsuleInfo->gi_type >= 3 ? 1 : capsuleInfo->gi_type); + const auto wonItemIdAndType = itemSelectionAlgorithm(capsuleInfo->gi_infoid, capsuleInfo->gi_price, + capsuleInfo->gi_type >= 3 ? 1 : capsuleInfo->gi_type, response.getMission() == Main::Enums::CapsuleSpinMission::CAPSULE_LUCKY_SPIN); if (!wonItemIdAndType || !Main::CdbUtils::itemExists(wonItemIdAndType->first)) { response.setExtra(Main::Enums::CapsuleSpinExtra::CAPSULE_SPIN_FAIL); @@ -151,7 +219,7 @@ namespace Main { std::string_view itemNameView{ *optName }; itemNameView = itemNameView.substr(0, itemNameView.find('\0')); - sessionsManager.broadcastMessageToLobby("[" + std::string{session->getAccountInfo().nickname} + "] won a [" + std::string{itemNameView} + sessionsManager.broadcastMessageToLobby("[" + std::string{ session->getAccountInfo().nickname } + "] won a [" + std::string{ itemNameView } + "] item from the capsule machine." ); } @@ -162,29 +230,30 @@ namespace Main Main::Structures::CapsuleSpin capsuleSpin{ wonItemIdAndType->first, serialInfo }; session->setLatestItemNumber(capsuleSpin.itemSerialInfo.itemNumber); Main::Structures::Item capsuleItem{ capsuleSpin }; - if (!(request.getOption() == 1 && response.getMission() == Main::Enums::CapsuleSpinMission::CAPSULE_LUCKY_SPIN)) // single lucky spin uses another handler apparently + if (session->addItem(capsuleItem)) { - if (session->addItem(capsuleItem)) - { - session->logItemInfo(capsuleItem.serialInfo.itemNumber, capsuleItem.itemId.itemId, capsuleItem.expirationDate, - "Item won through the capsule machine"); - } - else - { - session->sendMessage("Error while getting the item from the capsule machine - please report this issue!"); - ++retryCount; - continue; - } + session->logItemInfo(capsuleItem.serialInfo.itemNumber, capsuleItem.itemId.itemId, capsuleItem.expirationDate, + "Item won through the capsule machine"); } - + else + { + session->sendMessage("Error while getting the item from the capsule machine - please report this issue!"); + ++retryCount; + continue; + } + response.setData(reinterpret_cast(&capsuleSpin), sizeof(Main::Structures::CapsuleSpin)); session->asyncWrite(response); if (response.getMission() != Main::Enums::CapsuleSpinMission::CAPSULE_LUCKY_SPIN) { // lucky spin is free - removeCurrencyByCapsuleType(session, accountInfo, static_cast(capsuleInfo->gi_type), capsuleInfo->gi_price); + removeCurrencyByCapsuleType(session, accountInfo, static_cast(capsuleInfo->gi_type), capsuleInfo->gi_price, capsuleSaleEvent); session->addLuckyPoints(capsuleInfo->gi_luckypoint); ++i; } + if (request.getOption() == 1 && response.getMission() == Main::Enums::CAPSULE_LUCKY_SPIN) + { // single lucky spin + break; + } } } else diff --git a/MainServer/include/Handlers/Clan/ClanRoomLeaveHandler.h b/MainServer/include/Handlers/Clan/ClanRoomLeaveHandler.h index 7caf77b4..85363c76 100644 --- a/MainServer/include/Handlers/Clan/ClanRoomLeaveHandler.h +++ b/MainServer/include/Handlers/Clan/ClanRoomLeaveHandler.h @@ -77,7 +77,6 @@ namespace Main if (*res) { // the party must be closed since the only player that was in it left - selfClanRoom->broadcastMessageToWaitingPlayers("handlePartyRoomLeave: closing party room"); clansManager.removeExactRoom(ainfo.clanId, clanRoomNumber); return; } @@ -91,7 +90,6 @@ namespace Main if (room) { // clan vs clan room, notify all players about leaving - room->broadcastMessage("handlePartyRoomLeave: inside if (room) { ... }"); room->removePlayer(session, 27); } else if (!isLeaderLeaving) diff --git a/MainServer/include/Handlers/Clan/OtherClanJoinHandler.h b/MainServer/include/Handlers/Clan/OtherClanJoinHandler.h index c169aaaf..7e10f7d1 100644 --- a/MainServer/include/Handlers/Clan/OtherClanJoinHandler.h +++ b/MainServer/include/Handlers/Clan/OtherClanJoinHandler.h @@ -11,6 +11,7 @@ #include "../../Structures/Room/RoomJoinLatestInfo.h" #include #include "../Room/RoomJoinHandler.h" +#include namespace Main { diff --git a/MainServer/include/Handlers/EventsHandlers.h b/MainServer/include/Handlers/EventsHandlers.h index 2365497f..b417b298 100644 --- a/MainServer/include/Handlers/EventsHandlers.h +++ b/MainServer/include/Handlers/EventsHandlers.h @@ -6,6 +6,7 @@ #include "../Persistence/MainDatabaseManager.h" #include "../Classes/Player.h" #include "../../include/Network/MainSession.h" +#include namespace Main { diff --git a/MainServer/include/Handlers/Item/DeleteItemHandler.h b/MainServer/include/Handlers/Item/DeleteItemHandler.h index 711032b3..1f9dd075 100644 --- a/MainServer/include/Handlers/Item/DeleteItemHandler.h +++ b/MainServer/include/Handlers/Item/DeleteItemHandler.h @@ -4,6 +4,7 @@ #include "../../Network/MainSession.h" #include "../../../include/Structures/AccountInfo/MainAccountInfo.h" #include "Network/Packet.h" +#include namespace Main { diff --git a/MainServer/include/Handlers/Item/GambleItemHandler.h b/MainServer/include/Handlers/Item/GambleItemHandler.h new file mode 100644 index 00000000..7fa5fe41 --- /dev/null +++ b/MainServer/include/Handlers/Item/GambleItemHandler.h @@ -0,0 +1,93 @@ +#ifndef GAMBLE_ITEM_HANDLER_H +#define GAMBLE_ITEM_HANDLER_H + +#include "Network/Packet.h" +#include "../../../include/Network/MainSession.h" +#include "DeleteItemHandler.h" +#include +#include "../../Detail/Utilities.h" + +namespace Main +{ + namespace Handlers + { + inline void handleGambleItem(const Common::Network::Packet& request, std::shared_ptr session) + { + Common::Network::Packet response; + response.setTcpHeader(request.getSession(), Common::Enums::USER_LARGE_ENCRYPTION); + const auto& accountInfo = session->getAccountInfo(); + + constexpr const std::uint32_t gambleCost = 50'000; + if (accountInfo.microPoints < gambleCost) + { // send "not enough MP" message + response.setOrder(193); + response.setExtra(44); + session->asyncWrite(response); + return; + } + + if (session->getPlayer().getPlayerState() == Common::Enums::STATE_INVENTORY) + { + Main::Structures::ItemSerialInfo itemSerialInfo = Main::Details::parseData(request); + + using cdbItems = Common::ConstantDatabase::CdbSingleton; + using cdbWeapons = Common::ConstantDatabase::CdbSingleton; + + const auto& itemGambleItems = cdbItems::getGambleItems(); + const auto& weaponGambleItems = cdbWeapons::getGambleItems(); + + const auto foundId = session->getPlayer().findItemIdBySerialInfo(itemSerialInfo); + if (foundId == std::nullopt) + { + session->sendMessage("[Handlers::handleGambleItem] error: foundId == nullopt, please report this issue"); + return; + } + auto itemType = Main::CdbUtils::getItemType(foundId.value()); + if (itemType == std::nullopt) + { + session->sendMessage("[Handlers::handleGambleItem] error: itemType == nullopt, please report this issue"); + return; + } + if (Main::CdbUtils::getItemDuration(foundId.value()) != 0) + { + session->sendMessage("The gamble system can only be used with unlimited items"); + return; + } + + std::uint32_t newRandomId{}; + std::mt19937 gen(std::random_device{}()); + + if (itemGambleItems.contains(itemType.value()) && !itemGambleItems.at(itemType.value()).empty()) + { + const auto& items = itemGambleItems.at(itemType.value()); + std::uniform_int_distribution dist(0, items.size() - 1); + newRandomId = items[dist(gen)]; + } + else if (weaponGambleItems.contains(itemType.value()) && !weaponGambleItems.at(itemType.value()).empty()) + { + const auto& items = weaponGambleItems.at(itemType.value()); + std::uniform_int_distribution dist(0, items.size() - 1); + newRandomId = items[dist(gen)]; + } + else + { + session->sendMessage("[Handlers::handleGambleItem] Error: this item cannot be gambled!"); + return; + } + + if (session->replaceItem(itemSerialInfo, newRandomId, "Item replaced after using the gamble system (" + + std::to_string(foundId.value()) + " => " + std::to_string(newRandomId) + ")")) + { + session->setAccountMicroPoints(accountInfo.microPoints - gambleCost); + session->sendCurrency(); + } + else + { + session->sendMessage("[Handlers::handleGambleItem] error while replacing the item with a new one - report this issue"); + } + } + } + } +} + +#endif \ No newline at end of file diff --git a/MainServer/include/Handlers/Item/ItemRepairHandler.h b/MainServer/include/Handlers/Item/ItemRepairHandler.h new file mode 100644 index 00000000..d46766c8 --- /dev/null +++ b/MainServer/include/Handlers/Item/ItemRepairHandler.h @@ -0,0 +1,108 @@ +#ifndef REPAIR_ITEM_HANDLER_H +#define REPAIR_ITEM_HANDLER_H + +#include "../../Network/MainSession.h" +#include "../../../include/Structures/AccountInfo/MainAccountInfo.h" +#include "Network/Packet.h" +#include "../../Detail/Utilities.h" +#include + +namespace Main +{ + namespace Handlers + { + inline std::vector serializeItemRepair(const Main::ClientData::ItemRepair& itemRepair) + { + const std::size_t size = sizeof(itemRepair.newTotalRT) + sizeof(itemRepair.newTotalMP) + itemRepair.serialInfo.size() * sizeof(Main::Structures::ItemSerialInfo); + std::vector buffer(size); + + std::uint8_t* ptr = buffer.data(); + std::memcpy(ptr, &itemRepair.newTotalRT, sizeof(itemRepair.newTotalRT)); + ptr += sizeof(itemRepair.newTotalRT); + std::memcpy(ptr, &itemRepair.newTotalMP, sizeof(itemRepair.newTotalMP)); + ptr += sizeof(itemRepair.newTotalMP); + + if (!itemRepair.serialInfo.empty()) + { + std::memcpy(ptr, itemRepair.serialInfo.data(), itemRepair.serialInfo.size() * sizeof(Main::Structures::ItemSerialInfo)); + } + + return buffer; + } + + inline void handleItemRepair(const Common::Network::Packet& request, std::shared_ptr session) + { + Common::Network::Packet response; + response.setTcpHeader(session->getId(), Common::Enums::USER_ENCRYPTION); + response.setCommand(request.getOrder(), request.getMission(), 1, 1); + + auto ainfo = session->getAccountInfo(); + + Main::ClientData::ItemRepair itemRepair; + itemRepair.newTotalRT = ainfo.rockTotens; + itemRepair.newTotalMP = ainfo.microPoints; + itemRepair.serialInfo.reserve(request.getOption() * sizeof(Main::Structures::ItemSerialInfo)); + + // check whether enough MP + std::uint32_t totalMpNeeded = 0; + std::vector serialInfos; + serialInfos.reserve(request.getOption() * sizeof(Main::Structures::ItemSerialInfo)); + + for (std::size_t i = 0; i < request.getOption(); ++i) + { + auto serialInfo = Main::Details::parseData(request, i * sizeof(Main::Structures::ItemSerialInfo)); + serialInfos.push_back(serialInfo); + + if (auto idAndDurability = session->getPlayer().findItemIdAndDurabilityBySerialInfo(serialInfo)) + { + const auto baseItemDurability = Main::CdbUtils::getItemDurability(idAndDurability->first); + if (!baseItemDurability) + { + session->sendMessage("[Handlers::handleItemRepair] baseItemDurability was nullopt"); + continue; + } + + totalMpNeeded += *baseItemDurability - idAndDurability->second; + } + else + { + session->sendMessage("[Handlers::handleItemRepair] idAndDurability was nullopt"); + } + } + + if (ainfo.microPoints < totalMpNeeded) + { + itemRepair.serialInfo.clear(); + itemRepair.serialInfo.push_back(Main::Structures::ItemSerialInfo{}); + auto buffer = serializeItemRepair(itemRepair); + response.setData(buffer.data(), static_cast(buffer.size())); + session->asyncWrite(response); + return; + } + + // enough MP => repair everything + itemRepair.newTotalMP -= totalMpNeeded; + itemRepair.serialInfo = std::move(serialInfos); + + for (const auto& serialInfo : itemRepair.serialInfo) + { + if (auto idAndDurability = session->getPlayer().findItemIdAndDurabilityBySerialInfo(serialInfo)) + { + const auto baseItemDurability = Main::CdbUtils::getItemDurability(idAndDurability->first); + if (baseItemDurability) + { + session->updateItemDurability(serialInfo.itemNumber, *baseItemDurability); + } + } + } + + session->setAccountMicroPoints(itemRepair.newTotalMP); + auto buffer = serializeItemRepair(itemRepair); + response.setData(buffer.data(), static_cast(buffer.size())); + response.setOption(itemRepair.serialInfo.size()); + session->asyncWrite(response); + } + } +} + +#endif \ No newline at end of file diff --git a/MainServer/include/Handlers/Item/ItemUpgradeHandler.h b/MainServer/include/Handlers/Item/ItemUpgradeHandler.h index aa23e9fc..292f0e25 100644 --- a/MainServer/include/Handlers/Item/ItemUpgradeHandler.h +++ b/MainServer/include/Handlers/Item/ItemUpgradeHandler.h @@ -56,18 +56,17 @@ namespace Main } const auto& firstItemSerialInfo = itemSerialInfos.front(); - const auto firstItemIdOpt = session->getPlayer().findItemIdBySerialInfo(firstItemSerialInfo); + const auto firstItemIdOpt = session->getPlayer().findItemIdBySerialInfo(firstItemSerialInfo); // itemID of new weapon after upgrade if (!firstItemIdOpt) { session->sendMessage("[handleItemUpgrade] error: itemIdOpt was nullopt for first item: " + std::to_string(firstItemSerialInfo.itemNumber)); return; } - const auto mpNeeded = Common::ConstantDatabase::CdbSingleton::getInstance().getEntry(*firstItemIdOpt); - const auto hasParent = Common::ConstantDatabase::CdbSingleton::getInstance().getEntry(*firstItemIdOpt); - if (!mpNeeded || !hasParent) + const auto upgradeInfo = Common::ConstantDatabase::CdbSingleton::getInstance().getEntry(*firstItemIdOpt); + if (!upgradeInfo) { - session->sendMessage("[handleItemUpgrade] error: !mpNeeded or !hasParent for first item: " + std::to_string(firstItemSerialInfo.itemNumber)); + session->sendMessage("[handleItemUpgrade] error: upgradeInfo not found for itemID " + std::to_string(firstItemSerialInfo.itemNumber)); return; } @@ -88,12 +87,13 @@ namespace Main useGlue = *itemIdOpt == SpecialItems::SUPER_GLUE; } - session->upgradeWeapon(*firstItemIdOpt, firstItemSerialInfo, mpNeeded->ui_buy_point, hasParent->ui_parentid, request.getMission(), request.getOption(), - useEnergyRefund, useGlue); - - for (std::size_t i = 1; i < itemSerialInfos.size(); ++i) + if (session->upgradeWeapon(*firstItemIdOpt, firstItemSerialInfo, upgradeInfo->ui_parentid, request.getMission(), + request.getOption(), useEnergyRefund, useGlue)) { - session->deleteItem(itemSerialInfos[i], "Item deleted after it was used while upgrading a weapon (Example: Super Glue item"); + for (std::size_t i = 1; i < itemSerialInfos.size(); ++i) + { + session->deleteItem(itemSerialInfos[i], "Item deleted after it was used while upgrading a weapon (Example: Super Glue item"); + } } } else if (receivedExtra == 53) diff --git a/MainServer/include/Handlers/MainLobbyChatHandler.h b/MainServer/include/Handlers/MainLobbyChatHandler.h index edc2615e..8d9c0c20 100644 --- a/MainServer/include/Handlers/MainLobbyChatHandler.h +++ b/MainServer/include/Handlers/MainLobbyChatHandler.h @@ -7,7 +7,7 @@ #include "../Classes/RoomsManager.h" #include "../ChatCommands/ChatCommands.h" #include "../Classes/Room.h" - +#include #include namespace Main @@ -16,19 +16,23 @@ namespace Main { inline Main::Enums::ChatGrade getChatGrade(Common::Enums::PlayerGrade playerGrade) { - if (playerGrade == Main::Enums::PlayerGrade::GRADE_NORMAL) - { - return static_cast(playerGrade - 1); - } - else if (playerGrade == Main::Enums::PlayerGrade::GRADE_MOD || playerGrade == Main::Enums::PlayerGrade::GRADE_ES) - { - return static_cast(playerGrade - 2); - } - else if (playerGrade == Main::Enums::PlayerGrade::GRADE_TESTER) - { - return Main::Enums::ChatGrade::CHAT_TESTER; - } - return Main::Enums::ChatGrade::CHAT_GM; + if (playerGrade == Main::Enums::PlayerGrade::GRADE_NORMAL) + { + return static_cast(playerGrade - 1); + } + else if (playerGrade == Main::Enums::PlayerGrade::GRADE_MOD || playerGrade == Main::Enums::PlayerGrade::GRADE_ES) + { + return static_cast(playerGrade - 2); + } + else if (playerGrade == Main::Enums::PlayerGrade::GRADE_TESTER) + { + return Main::Enums::ChatGrade::CHAT_TESTER; + } + else if (playerGrade == Main::Enums::PlayerGrade::GRADE_GM) + { + return Main::Enums::ChatGrade::CHAT_GM; + } + return Main::Enums::ChatGrade::CHAT_NORMAL; } inline void executeCommand(std::shared_ptr session, const Common::Network::Packet& request, Common::Network::Packet& response, @@ -36,7 +40,7 @@ namespace Main Main::Persistence::MainScheduler& scheduler, const Main::Network::Session::AccountInfo& accountInfo, Main::MainServer& mainSv) { const std::string command{ reinterpret_cast(request.getData() + 1), static_cast(request.getOption() - 1) }; - if (command == "commands") + if (command == "commands" || command == "?") { Main::Command::ChatCommands::showUsages(session, response, static_cast(accountInfo.playerGrade)); return; diff --git a/MainServer/include/Handlers/Player/AuthorizationHandler.h b/MainServer/include/Handlers/Player/AuthorizationHandler.h index 45fd76e4..0e3ce2df 100644 --- a/MainServer/include/Handlers/Player/AuthorizationHandler.h +++ b/MainServer/include/Handlers/Player/AuthorizationHandler.h @@ -7,90 +7,136 @@ #include "Utils/Constants.h" #include #include -#include -#include #include + +#ifdef _WIN32 + #include + #include +#else + #include +#endif + #include "../../Detail/Utilities.h" #include + namespace Main { - namespace Handlers - { - std::string ipToString(std::uint32_t ip) - { - in_addr addr; - addr.S_un.S_addr = ip; - - char str[INET_ADDRSTRLEN]; - inet_ntop(AF_INET, &addr, str, INET_ADDRSTRLEN); - - return std::string(str); - } - - inline std::optional handleAuthorization(const Common::Network::Packet& request, std::shared_ptr session, - std::size_t totalOnlinePlayers, bool isServerOffline, Main::Persistence::MainScheduler& scheduler, bool isPublic) - { - START_BENCHMARK - - Common::Network::Packet response; - response.setTcpHeader(request.getSession(), Common::Enums::NO_ENCRYPTION); - response.setOrder(request.getOrder()); - response.setExtra(static_cast(Main::Enums::AuthorizationExtra::SUCCESS)); - - if (request.getDataSize() < sizeof(Main::ClientData::ClientAuthorization)) - { - response.setExtra(static_cast(Main::Enums::AuthorizationExtra::AUTHORIZATION_FAILED)); - session->asyncWrite(response); - return std::nullopt; - } - - const auto clientInfo = Main::Details::parseData(request); - const auto& clientVersionRequired = Common::Utils::SetupParser::getInstance().getClientSetup(); - - if (auto accountInfoOpt = - scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getPlayerInfo, clientInfo.accountID); accountInfoOpt) - { - const bool clientVersionMatches = clientInfo.clientVersion.matches(clientVersionRequired.version1, - clientVersionRequired.version2, clientVersionRequired.version3); - const bool serverUnavailable = totalOnlinePlayers >= Common::Constants::maxServerCapacity || isServerOffline; - - if ((serverUnavailable || !clientVersionMatches || !isPublic) && accountInfoOpt->playerGrade < Common::Enums::PlayerGrade::GRADE_MOD) - { - response.setExtra(static_cast(Main::Enums::AuthorizationExtra::WRONG_CLIENT_VER_OR_SERVER_FULL_OR_OFFLINE)); - session->asyncWrite(response); - - END_BENCHMARK(handleAuthorization, session) - return std::nullopt; - } - - else if (accountInfoOpt->accountID != clientInfo.accountID || clientInfo.accountHash != accountInfoOpt->accountKey) - { - response.setExtra(static_cast(Main::Enums::AuthorizationExtra::AUTHORIZATION_FAILED)); - session->asyncWrite(response); - return std::nullopt; - } - - auto hasBeenMatchBannedOpt = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::hasBeenMatchBanned, - accountInfoOpt->accountID); - if (hasBeenMatchBannedOpt == std::nullopt) - { - session->closeSocket(); - return std::nullopt; - } - session->setHasBeenMatchBanned(*hasBeenMatchBannedOpt); - session->asyncWrite(response); - session->sendMessage("Welcome! To see all commands, type /?", Main::Enums::ChatExtra::INFO); - session->sendMessage("Client Version: " + std::to_string(clientInfo.clientVersion.ver2) + "." - + std::to_string(clientInfo.clientVersion.ver3) + "." + std::to_string(clientInfo.clientVersion.ver4)); - - END_BENCHMARK(handleAuthorization, session) - return *accountInfoOpt; - } - - END_BENCHMARK(handleAuthorization, session) - return std::nullopt; - } - } + namespace Handlers + { + inline std::string ipToString(std::uint32_t ip) + { + in_addr addr; +#ifdef _WIN32 + addr.S_un.S_addr = ip; +#else + addr.s_addr = ip; +#endif + char str[INET_ADDRSTRLEN]; + inet_ntop(AF_INET, &addr, str, INET_ADDRSTRLEN); + return std::string(str); + } + + inline std::optional handleAuthorization( + const Common::Network::Packet& request, + std::shared_ptr session, + std::size_t totalOnlinePlayers, + bool isServerOffline, + Main::Persistence::MainScheduler& scheduler, + bool isPublic, + Main::Classes::ReportManager& reportManager) + { + START_BENCHMARK + + Common::Network::Packet response; + response.setTcpHeader(request.getSession(), Common::Enums::NO_ENCRYPTION); + response.setOrder(request.getOrder()); + response.setExtra(static_cast(Main::Enums::AuthorizationExtra::SUCCESS)); + + if (request.getDataSize() < sizeof(Main::ClientData::ClientAuthorization)) + { + response.setExtra(static_cast(Main::Enums::AuthorizationExtra::AUTHORIZATION_FAILED)); + session->asyncWrite(response); + return std::nullopt; + } + + const auto clientInfo = Main::Details::parseData(request); + const auto& clientVersionRequired = Common::Utils::SetupParser::getInstance().getClientSetup(); + + if (auto accountInfoOpt = + scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getPlayerInfo, clientInfo.accountID); + accountInfoOpt) + { + const bool clientVersionMatches = clientInfo.clientVersion.matches( + clientVersionRequired.version1, + clientVersionRequired.version2, + clientVersionRequired.version3 + ); + const bool serverUnavailable = totalOnlinePlayers >= Common::Constants::maxServerCapacity || isServerOffline; + + if ((serverUnavailable || !clientVersionMatches || !isPublic) && accountInfoOpt->playerGrade < Common::Enums::PlayerGrade::GRADE_MOD) + { + response.setExtra(static_cast(Main::Enums::AuthorizationExtra::WRONG_CLIENT_VER_OR_SERVER_FULL_OR_OFFLINE)); + session->asyncWrite(response); + END_BENCHMARK(handleAuthorization, session) + return std::nullopt; + } + + else if (accountInfoOpt->accountID != clientInfo.accountID || clientInfo.accountHash != accountInfoOpt->accountKey) + { + response.setExtra(static_cast(Main::Enums::AuthorizationExtra::AUTHORIZATION_FAILED)); + session->asyncWrite(response); + return std::nullopt; + } + + auto hasBeenMatchBannedOpt = scheduler.immediatePersist( + std::source_location::current(), + &Main::Persistence::PersistentDatabase::hasBeenMatchBanned, + accountInfoOpt->accountID + ); + + if (hasBeenMatchBannedOpt == std::nullopt) + { + session->closeSocket(); + return std::nullopt; + } + + session->setHasBeenMatchBanned(*hasBeenMatchBannedOpt); + session->asyncWrite(response); + session->sendMessage("Welcome! To see all commands, type /commands", Main::Enums::ChatExtra::INFO); + + Common::Enums::PlayerGrade playerGrade = static_cast(accountInfoOpt->playerGrade); + if (playerGrade == Common::Enums::PlayerGrade::GRADE_MOD || + playerGrade == Common::Enums::PlayerGrade::GRADE_TESTER || + playerGrade == Common::Enums::PlayerGrade::GRADE_GM) + { + std::size_t pendingReports = reportManager.getUnacknowledgedReportsCount(); + if (pendingReports > 0) + { + session->sendMessage("You got " + std::to_string(pendingReports) + " pending reports /reports", Main::Enums::TIP); + } + else + { + session->sendMessage("No pending reports", Main::Enums::TIP); + } + } + + + session->sendMessage( + "Client Version: " + + std::to_string(clientInfo.clientVersion.ver2) + "." + + std::to_string(clientInfo.clientVersion.ver3) + "." + + std::to_string(clientInfo.clientVersion.ver4) + ); + + END_BENCHMARK(handleAuthorization, session) + return *accountInfoOpt; + } + + END_BENCHMARK(handleAuthorization, session) + return std::nullopt; + } + } } -#endif \ No newline at end of file +#endif + diff --git a/MainServer/include/Handlers/Player/EquippedItemsHandler.h b/MainServer/include/Handlers/Player/EquippedItemsHandler.h index 47ce6b61..49e51844 100644 --- a/MainServer/include/Handlers/Player/EquippedItemsHandler.h +++ b/MainServer/include/Handlers/Player/EquippedItemsHandler.h @@ -17,13 +17,10 @@ namespace Main if (request.getExtra() == 51) { - std::uint16_t character; - std::uint32_t itemNumber; - for (std::size_t idx = 0; idx < request.getOption(); ++idx) { - std::memcpy(&character, request.getData() + idx * 12, sizeof(character)); - std::memcpy(&itemNumber, request.getData() + idx * 12 + 4, sizeof(itemNumber)); + const std::uint16_t character = Main::Details::parseData(request, idx * 12); + const std::uint32_t itemNumber = Main::Details::parseData(request, idx * 12 + 4); session->switchItemEquip(character, itemNumber); } } @@ -33,13 +30,11 @@ namespace Main } else if (request.getMission() == 0) { - Main::Structures::ItemSerialInfo itemSerialInfo; - std::uint64_t val = 0; - for (std::size_t idx = 0; idx < request.getOption(); ++idx) { - std::memcpy(&itemSerialInfo, request.getData() + idx * 8, sizeof(itemSerialInfo)); - std::memcpy(&val, request.getData() + idx * 8, sizeof(itemSerialInfo)); + const Main::Structures::ItemSerialInfo itemSerialInfo = Main::Details::parseData(request, idx * 8); + const std::uint64_t val = Main::Details::parseData(request, idx * 8); + if (val < static_cast(Common::Constants::maxItemType)) { // val == equippedItem.type session->unequipItem(val); diff --git a/MainServer/include/Handlers/Player/InitialPlayerInfoHandlers.h b/MainServer/include/Handlers/Player/InitialPlayerInfoHandlers.h index 72e815b8..4ce4711e 100644 --- a/MainServer/include/Handlers/Player/InitialPlayerInfoHandlers.h +++ b/MainServer/include/Handlers/Player/InitialPlayerInfoHandlers.h @@ -16,16 +16,12 @@ namespace Main inline void handleInitialPlayerInfos(const Common::Network::Packet& request, std::shared_ptr session, Main::Network::SessionsManager& sessionsManager, Main::Persistence::MainScheduler& scheduler, std::uint64_t timeSinceLastServerRestart, std::uint32_t serverId, bool isServerOffline, bool isPublic, const Main::Structures::EventMissionInfo& eventMissionInfo, - const Main::Structures::ExpMpBonusInfo& expMpBonusInfo) + const Main::Structures::ExpMpBonusInfo& expMpBonusInfo, Main::Classes::ReportManager& reportManager) { - if (auto accountInfo = handleAuthorization(request, session, sessionsManager.getAllSessions().size(), isServerOffline, scheduler, isPublic); accountInfo) + if (auto accountInfo = handleAuthorization(request, session, sessionsManager.getAllSessions().size(), isServerOffline, scheduler, isPublic, reportManager); accountInfo) { - START_BENCHMARK - handleAccountInformation(request, session, sessionsManager, scheduler, *accountInfo, timeSinceLastServerRestart, serverId); - session->sendInventory(*reinterpret_cast(request.getData())); - handleAccountInformation(request, session, sessionsManager, scheduler, *accountInfo, timeSinceLastServerRestart, serverId, 59); session->sendWeeklyReward(); @@ -43,13 +39,10 @@ namespace Main Main::Handlers::handleModeEvents(request, session, scheduler); Main::Handlers::handleMapEvents(request, session, scheduler); } - - - END_BENCHMARK(handleInitialPlayerInfos, session) } } } } -#endif \ No newline at end of file +#endif diff --git a/MainServer/include/Handlers/Player/Mailbox/MailboxHandlers.h b/MainServer/include/Handlers/Player/Mailbox/MailboxHandlers.h index 976e8ca7..f52e0a52 100644 --- a/MainServer/include/Handlers/Player/Mailbox/MailboxHandlers.h +++ b/MainServer/include/Handlers/Player/Mailbox/MailboxHandlers.h @@ -6,6 +6,7 @@ #include "../../../../include/MainEnums.h" #include "../../../Network/MainSessionManager.h" #include "Utils/Constants.h" +#include namespace Main { @@ -30,6 +31,9 @@ namespace Main const Main::ClientData::MailboxMessage& mailboxData) { START_BENCHMARK + // Check if the sender is muted - if so, don't allow mailbox communication + if (session->getPlayer().isMuted()) return; + if (request.getOption() == 2) // option seems to always be 2 { auto targetSession = sessionsManager.findSessionByName(mailboxData.nickname); diff --git a/MainServer/include/Handlers/Player/MainAccountInfoHandler.h b/MainServer/include/Handlers/Player/MainAccountInfoHandler.h index c111b66f..0e91649c 100644 --- a/MainServer/include/Handlers/Player/MainAccountInfoHandler.h +++ b/MainServer/include/Handlers/Player/MainAccountInfoHandler.h @@ -34,7 +34,6 @@ namespace Main accountInfo.uniqueId.session = session->getId(); accountInfo.uniqueId.server = serverId; accountInfo.serverTime = accountInfo.getUtcTimeMs() - timeSinceLastServerRestart; - session->setAccountInfo(accountInfo); sessionsManager.addSession(session); @@ -59,6 +58,10 @@ namespace Main { session->setRoomCreationDisabled(); } + if (scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::isVotekickDisabled, accountInfo.accountID)) + { + session->setVotekickDisabled(); + } auto [sentMailboxes, receivedMailboxes] = scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::loadMailboxes, accountInfo.accountID); session->setMailbox(sentMailboxes, true); diff --git a/MainServer/include/Handlers/Player/PlayerStateHandler.h b/MainServer/include/Handlers/Player/PlayerStateHandler.h index eb602fef..6a7ba158 100644 --- a/MainServer/include/Handlers/Player/PlayerStateHandler.h +++ b/MainServer/include/Handlers/Player/PlayerStateHandler.h @@ -29,17 +29,17 @@ namespace Main if (static_cast(request.getOption()) == Common::Enums::PlayerState::STATE_CAPSULE) { // capsule resends currency + eventual sales + session->sendCurrency(); + constexpr std::size_t chunkSize = 50; auto capsuleItems = CdbUtils::getCapsuleEvents(capsuleListDb.saleEventStartDate, capsuleListDb.saleEventEndDate, capsuleListDb.newMpPrice, capsuleListDb.newRtPrice); - std::size_t totalItems = capsuleItems.size(); + const std::size_t totalItems = capsuleItems.size(); response.setOrder(83); for (std::size_t i = 0; i < totalItems; i += chunkSize) { - session->sendCurrency(); - - std::size_t currentChunkSize = std::min(chunkSize, totalItems - i); + const std::size_t currentChunkSize = std::min(chunkSize, totalItems - i); auto* chunkData = reinterpret_cast(&capsuleItems[i]); response.setData(chunkData, currentChunkSize * sizeof(Main::Structures::CapsuleList)); response.setOption(currentChunkSize); @@ -49,6 +49,16 @@ namespace Main if (Main::Classes::Room* room = roomsManager.getRoomByNumber(session->getPlayer().getRoomNumber())) { + std::uint32_t playerState = request.getOption(); + if (session->getPlayer().isInMatch() && + (playerState == Common::Enums::PlayerState::STATE_CAPSULE || + playerState == Common::Enums::PlayerState::STATE_INVENTORY || + playerState == Common::Enums::PlayerState::STATE_READY || + playerState == Common::Enums::PlayerState::STATE_SHOP)) + { + session->sendMessage("[handlerPlayerState] Attempted to switch to new state: [" + std::to_string(playerState) + "] while inside a match!"); + return; + } const auto uniqueId = session->getAccountInfo().uniqueId; response.setCommand(Details::Orders::PLAYER_STATE_NOTIFICATION, 0, 0, static_cast(request.getOption())); response.setData(reinterpret_cast(&uniqueId), sizeof(uniqueId)); diff --git a/MainServer/include/Handlers/Player/characterSelectionHandler.h b/MainServer/include/Handlers/Player/characterSelectionHandler.h index 1868eaf4..ce2eae45 100644 --- a/MainServer/include/Handlers/Player/characterSelectionHandler.h +++ b/MainServer/include/Handlers/Player/characterSelectionHandler.h @@ -15,8 +15,8 @@ namespace Main Main::Classes::RoomsManager& roomsManager) { const std::uint32_t selectedCharacter = static_cast(request.getOption()); - const bool isCharacterAvailable = selectedCharacter == Common::Enums::Naomi || selectedCharacter == Common::Enums::Pandora - || selectedCharacter == Common::Enums::CHIP || selectedCharacter == Common::Enums::Knox || selectedCharacter == Common::Enums::Kai; + const bool isCharacterAvailable = true; //selectedCharacter == Common::Enums::Naomi || selectedCharacter == Common::Enums::Pandora + //|| selectedCharacter == Common::Enums::CHIP || selectedCharacter == Common::Enums::Knox || selectedCharacter == Common::Enums::Kai; Common::Network::Packet response; response.setTcpHeader(request.getSession(), Common::Enums::USER_LARGE_ENCRYPTION); diff --git a/MainServer/include/Handlers/Room/ClanRoomCreation.h b/MainServer/include/Handlers/Room/ClanRoomCreation.h index e890de92..7ae0afdb 100644 --- a/MainServer/include/Handlers/Room/ClanRoomCreation.h +++ b/MainServer/include/Handlers/Room/ClanRoomCreation.h @@ -8,12 +8,12 @@ #include "../../Classes/RoomsManager.h" #include "../../Classes/Room.h" #include "../../Detail/IpcUtils.h" +#include namespace Main { namespace Handlers { - inline bool handleRoomCreationClan(const Common::Network::Packet& request, std::shared_ptr session, Main::Classes::RoomsManager& roomsManager) { @@ -27,8 +27,7 @@ namespace Main roomCreator.ping = session->getPlayer().getPing(); roomCreator.team = Common::Enums::TEAM_BLUE; - Main::Structures::CompleteRoomInfo roomInfo; - std::memcpy(&roomInfo, request.getData(), request.getDataSize()); + const Main::Structures::CompleteRoomInfo roomInfo = Main::Details::parseData(request); // request.getOption() ==> server/channel ID Main::Classes::Room room{ roomInfo.title, roomInfo.roomSettings, roomCreator, session, true }; @@ -41,27 +40,21 @@ namespace Main room.setSpecificSetting(request.getExtra()); // Send room number to cast server - if (Main::Ipc::M2C_sendRoomNumber(session->getId(), room.getRoomNumber())) - { - Common::Network::Packet response; - response.setTcpHeader(request.getSession(), Common::Enums::NO_ENCRYPTION); - response.setOrder(request.getOrder()); - room.setStateFor(session->getAccountInfo().uniqueId, Common::Enums::STATE_WAITING); - session->setRoomNumber(room.getRoomNumber()); - const std::pair roomNum{ room.getRoomNumber() - 1, 1 }; // {roomNum, unk} - response.setExtra(1); - response.setData(reinterpret_cast(&roomNum), sizeof(roomNum)); - session->asyncWrite(response); + Main::Ipc::M2C_sendRoomNumber(session->getId(), room.getRoomNumber()); + Common::Network::Packet response; + response.setTcpHeader(request.getSession(), Common::Enums::NO_ENCRYPTION); + response.setOrder(request.getOrder()); + room.setStateFor(session->getAccountInfo().uniqueId, Common::Enums::STATE_WAITING); + session->setRoomNumber(room.getRoomNumber()); + const std::pair roomNum{ room.getRoomNumber() - 1, 1 }; // {roomNum, unk} + response.setExtra(1); + response.setData(reinterpret_cast(&roomNum), sizeof(roomNum)); + session->asyncWrite(response); - roomsManager.addRoom(std::move(room)); - return true; - } - else - { - return false; - } + roomsManager.addRoom(std::move(room)); + return true; } } } -#endif \ No newline at end of file +#endif diff --git a/MainServer/include/Handlers/Room/EliminationNextRoundHandler.h b/MainServer/include/Handlers/Room/EliminationNextRoundHandler.h index 69538e7e..3d87196d 100644 --- a/MainServer/include/Handlers/Room/EliminationNextRoundHandler.h +++ b/MainServer/include/Handlers/Room/EliminationNextRoundHandler.h @@ -63,6 +63,103 @@ namespace Main } } + inline void handleBossBattleEnding(Main::Classes::Room* room, Common::Network::Packet& response, bool isFarm) + { + if (!isFarm) + { + auto data = response.getData(); + isFarm = (response.getDataSize() > 8) ? (data[8] == 5) : true; + } + + const std::uint32_t totalPlayers = room->getPlayersCount(); + enum Rewards { GoldPveBox = 4801012, SilverPveBox = 4801013, BronzePveBox = 4801014 }; + + static std::random_device rd; + static std::mt19937 gen(rd()); + static std::uniform_int_distribution dist(0, 2); + + struct PlayerInfo + { + Main::Structures::UniqueId uid; + std::uint32_t wonBoxId; + }; + + std::vector playerInfos; + playerInfos.reserve(totalPlayers); + + for (auto& [roomInfo, session] : room->getAllPlayersWithSessions()) + { + if (roomInfo.team == Common::Enums::TEAM_OBSERVER || !session->getPlayer().isInMatch()) continue; + + std::uint32_t reward; + switch (dist(gen)) { + case 0: reward = GoldPveBox; break; + case 1: reward = SilverPveBox; break; + default: reward = BronzePveBox; break; + } + playerInfos.push_back({ session->getAccountInfo().uniqueId, reward }); + } + + // self rewards + struct PveResponseSelf + { + std::uint32_t totMP; + std::uint32_t totEXP; + std::uint32_t rewardId; + } pveRespSelf; + + response.setExtra(isFarm ? 6 : 1); + for (auto& [roomInfo, session] : room->getAllPlayersWithSessions()) + { + if (roomInfo.team == Common::Enums::TEAM_OBSERVER || !session->getPlayer().isInMatch()) continue; + + pveRespSelf.totMP = session->getAccountInfo().microPoints; + pveRespSelf.totEXP = session->getAccountInfo().experience; + auto it = std::find_if(playerInfos.begin(), playerInfos.end(), [&](const PlayerInfo& info) { + return info.uid == session->getAccountInfo().uniqueId;}); + pveRespSelf.rewardId = (it != playerInfos.end()) ? it->wonBoxId : 0; + response.setData(reinterpret_cast(&pveRespSelf), sizeof(pveRespSelf)); + session->asyncWrite(response); + + if (pveRespSelf.rewardId && !isFarm) + { + session->spawnItemCommand(pveRespSelf.rewardId, "Boss Battle reward spawned automatically after completing the boss battle mode"); + session->sendRt(2000); + session->sendMessage("You obtained 2'000 RT and a Boss Battle reward!"); + } + } + if (isFarm) return; + response.setData(nullptr, 0); + + // others' rewards + response.setExtra(41); + for (auto& [roomInfo, session] : room->getAllPlayersWithSessions()) + { + if (roomInfo.team == Common::Enums::TEAM_OBSERVER) continue; + + auto selfId = session->getAccountInfo().uniqueId; + std::vector filtered; + filtered.reserve(playerInfos.size() - 1); + for (auto& pi : playerInfos) + { + if (pi.uid != selfId) filtered.push_back(pi); + } + + std::uint32_t count = static_cast(filtered.size()); + std::vector buffer; + buffer.reserve(sizeof(count) + filtered.size() * sizeof(PlayerInfo)); + buffer.insert(buffer.end(), reinterpret_cast(&count), reinterpret_cast(&count) + sizeof(count)); + buffer.insert(buffer.end(), reinterpret_cast(filtered.data()), + reinterpret_cast(filtered.data()) + filtered.size() * sizeof(PlayerInfo)); + + response.setData(buffer.data(), buffer.size()); + session->asyncWrite(response); + } + + response.setData(nullptr, 0); + } + + inline void handleMatchEnding(const Common::Network::Packet& request, std::shared_ptr session, Main::Classes::RoomsManager& roomsManager, Main::Classes::ClansManager& clansManager, Main::Persistence::MainScheduler& scheduler, const Main::Structures::ExpMpBonusInfo& expMpBonusInfo, @@ -83,10 +180,10 @@ namespace Main if (session->getPlayer().hasEnoughInventorySpace(1)) { - constexpr std::uint64_t fiveMinsMs = 5 * 60 * 1000; - constexpr std::uint64_t tenMinsMs = 10 * 60 * 1000; + constexpr std::uint64_t threeMinsMs = 3 * 60 * 1000; + constexpr std::uint64_t sevenMinsMs = 7 * 60 * 1000; - if (req.type == 1 && req.stage == 10 && (now - session->m_matchStartTime >= fiveMinsMs)) + if (req.type == 1 && req.stage == 10 && (now - session->m_matchStartTime >= threeMinsMs)) { Main::Structures::BoughtItem reward{ Common::Constants::singlewaveEasyBox }; reward.serialInfo.itemNumber = session->getPlayer().getLatestItemNumber() + 1; @@ -97,7 +194,7 @@ namespace Main } else if (req.type == 2) { - if (req.stage == 20 && (now - session->m_matchStartTime >= tenMinsMs)) + if (req.stage == 20 && (now - session->m_matchStartTime >= sevenMinsMs)) { Main::Structures::BoughtItem reward{ Common::Constants::singlewaveHardBox }; reward.serialInfo.itemNumber = session->getPlayer().getLatestItemNumber() + 1; @@ -142,20 +239,26 @@ namespace Main session->sendMessage("[Main::Handlers::handleMatchEnding] error while retrieving either clan room!"); } } - room->endMatch(); - bool isFarm = false; const std::uint64_t roomStartTime = room->getMatchStartTime(); const std::uint64_t timeNow = Main::Details::getUtcTimeMs(); - if ((timeNow > roomStartTime && ((timeNow - roomStartTime) < 120 * 1000)) || room->getRoomSettings().mode == Common::Enums::SquareMode + if ((timeNow > roomStartTime && ((timeNow - roomStartTime) < 80 * 1000)) || room->getRoomSettings().mode == Common::Enums::SquareMode || room->getRoomSettings().mode == Common::Enums::AiBattle) - { // prevent farming + { isFarm = true; } // client sends all info of all players, we resend it back to everyone (else the other clients outside the match will see the target still inside the match) room->broadcastToRoomExceptSelf(response, session->getAccountInfo().uniqueId); + if (room->getRoomSettings().mode == Common::Enums::BossBattle) + { + handleBossBattleEnding(room, response, isFarm); + room->endMatch(); + return; + } + + room->endMatch(); for (std::size_t i = 0; i < request.getOption(); ++i) { @@ -184,15 +287,23 @@ namespace Main totalMpBonus += mpBonus; } - const auto gainedMp = isFarm ? 0 : ((scoreboardResponse.totalKills * 15 + scoreboardResponse.deaths * 5 + Common::Constants::matchBaseMp) * 2); - const auto gainedExp = isFarm ? 0 : ((scoreboardResponse.totalKills * 10 + scoreboardResponse.deaths * 5 + Common::Constants::matchBaseExp) * 2); + auto baseMp = (scoreboardResponse.totalKills * 15 + scoreboardResponse.deaths * 5 + Common::Constants::matchBaseMp) * 2; + auto baseExp = (scoreboardResponse.totalKills * 15 + scoreboardResponse.deaths * 5 + Common::Constants::matchBaseExp) * 2; - const auto finalGainedExp = gainedExp + (gainedExp * totalExpBonus / 100); - const auto finalGainedMp = gainedMp + (gainedMp * totalMpBonus / 100); + const auto elapsedMs = timeNow - targetSession->m_matchStartTime; + auto elapsedMinutes = static_cast(elapsedMs / 1000 / 60); + elapsedMinutes = std::min(elapsedMinutes, 15); + constexpr std::uint64_t mpPerMinute = 80; + constexpr std::uint64_t expPerMinute = 80; + auto gainedMp = isFarm ? 0 : baseMp + (elapsedMinutes * mpPerMinute); + auto gainedExp = isFarm ? 0 : baseExp + (elapsedMinutes * expPerMinute); + auto finalGainedExp = gainedExp + (gainedExp * totalExpBonus / 100); + auto finalGainedMp = gainedMp + (gainedMp * totalMpBonus / 100); std::uint32_t now = static_cast(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); - std::uint32_t finalGainedExpWithEvent = finalGainedExp; - std::uint32_t finalGainedMpWithEvent = finalGainedMp; + auto mode = room->getRoomSettings().mode; + std::uint32_t finalGainedExpWithEvent = mode == Common::Enums::FreeForAll ? (finalGainedExp / 2) : finalGainedExp; + std::uint32_t finalGainedMpWithEvent = mode == Common::Enums::FreeForAll ? (finalGainedMp / 2) : finalGainedMp; if (now <= expMpBonusInfo.endDate) { @@ -204,8 +315,24 @@ namespace Main : Common::Constants::maxExpAndMpPerMatch; const auto clampedMp = (finalGainedMpWithEvent <= Common::Constants::maxExpAndMpPerMatch) ? finalGainedMpWithEvent : Common::Constants::maxExpAndMpPerMatch; - scoreboardResponse.newTotalEXP = ainfo.experience + clampedExp; - scoreboardResponse.newTotalMP = ainfo.microPoints + clampedMp; + if (!isFarm && (clampedExp + ainfo.experience) < ainfo.experience) + { + targetSession->sendMessage("[Handlers::handleEliminationNextRound] error: negative experience detected"); + scoreboardResponse.newTotalEXP = ainfo.experience + 200; + } + else + { + scoreboardResponse.newTotalEXP = ainfo.experience + clampedExp; + } + if (!isFarm && (clampedMp + ainfo.microPoints) < ainfo.microPoints) + { + targetSession->sendMessage("[Handlers::handleEliminationNextRound] error: negative MP detected"); + scoreboardResponse.newTotalMP = ainfo.microPoints + 200; + } + else + { + scoreboardResponse.newTotalMP = ainfo.microPoints + clampedMp; + } if (auto* gradeInfo = CD::CdbSingleton::getInstance().getEntry(ainfo.playerLevel + 1); !isFarm && gradeInfo && scoreboardResponse.newTotalEXP >= gradeInfo->gi_exp) @@ -228,10 +355,11 @@ namespace Main eventMissionInfo); if (actualPlayerLevel >= 5 && actualPlayerLevel % 5 == 0) - { // RT reward + { // RT & coupon reward const std::uint32_t rtToAdd = 2000 * (actualPlayerLevel / 5); targetSession->sendRt(rtToAdd); - targetSession->sendMessage("You obtained " + std::to_string(rtToAdd) + " RockTotens!"); + targetSession->spawnCouponImmediate(5); + targetSession->sendMessage("You obtained " + std::to_string(rtToAdd) + " RockTokens and 5 coupons!"); } } else @@ -242,10 +370,17 @@ namespace Main eventMissionInfo); } } - response.setCommand(request.getOrder(), 3, isFarm ? 6 : 1, 0); response.setData(reinterpret_cast(&scoreboardResponse), sizeof(scoreboardResponse)); targetSession->asyncWrite(response); + if (!isFarm) + { + targetSession->sendRt(static_cast(static_cast(clampedMp)/3)); + if (room->getRoomSettings().mode != Common::Enums::SquareMode && room->getRoomSettings().mode != Common::Enums::AiBattle) + { + targetSession->reduceEquippedItemsDurability(room->getRoomSettings().weaponRestriction); + } + } } } } diff --git a/MainServer/include/Handlers/Room/MatchLeaveHandler.h b/MainServer/include/Handlers/Room/MatchLeaveHandler.h index 47354ada..73ffa219 100644 --- a/MainServer/include/Handlers/Room/MatchLeaveHandler.h +++ b/MainServer/include/Handlers/Room/MatchLeaveHandler.h @@ -65,7 +65,7 @@ namespace Main Main::Details::parseData(request, request.getDataSize() - sizeof(Main::Structures::ItemSerialInfo)); session->useNoPenalty(itemSerialInfo, request); } - else + else if (room->getTeamForSession(session->getId()).value_or(0) != Common::Enums::TEAM_OBSERVER) { // remove penalty mp const auto currentMp = session->getPlayer().getAccountInfo().microPoints; session->setAccountMicroPoints(currentMp <= 120 ? 0 : currentMp - 120); diff --git a/MainServer/include/Handlers/Room/RoomCreationHandler.h b/MainServer/include/Handlers/Room/RoomCreationHandler.h index 49a0702f..6e02cdc2 100644 --- a/MainServer/include/Handlers/Room/RoomCreationHandler.h +++ b/MainServer/include/Handlers/Room/RoomCreationHandler.h @@ -8,6 +8,7 @@ #include "../../Classes/RoomsManager.h" #include "../../Classes/Room.h" #include "../../Detail/IpcUtils.h" +#include namespace Main { @@ -24,12 +25,17 @@ namespace Main inline void handleRoomCreation(const Common::Network::Packet& request, std::shared_ptr session, Main::Classes::RoomsManager& roomsManager, bool isRoomCreationEnabled) { - if (!isRoomCreationEnabled && session->getAccountInfo().playerGrade < Common::Enums::GRADE_ES) + if (session->getPlayer().getRoomNumber()) // cannot create a room while already in one + { + session->sendMessage("You are already in a room - If this is an error, report it"); + return; + } + else if (!isRoomCreationEnabled && session->getAccountInfo().playerGrade < Common::Enums::GRADE_ES) { session->sendMessage("Public room creation is currently disabled by the team."); return; } - if (!session->getPlayer().isRoomCreationEnabled()) + else if (!session->getPlayer().isRoomCreationEnabled()) { session->sendMessage("You currently cannot create a new room."); return; @@ -49,8 +55,7 @@ namespace Main roomCreator.ping = session->getPlayer().getPing(); roomCreator.team = Common::Enums::TEAM_ALL; - Main::Structures::CompleteRoomInfo roomInfo; - std::memcpy(&roomInfo, request.getData(), request.getDataSize()); + const Main::Structures::CompleteRoomInfo roomInfo = Main::Details::parseData(request); // request.getOption() ==> server/channel ID Main::Classes::Room room{ roomInfo.title, roomInfo.roomSettings, roomCreator, session }; @@ -74,17 +79,18 @@ namespace Main response.setExtra(RoomCreationExtra::CREATION_FAIL); session->asyncWrite(response); } - else if (Main::Ipc::M2C_sendRoomNumber(session->getId(), room.getRoomNumber())) + else { + Main::Ipc::M2C_sendRoomNumber(session->getId(), room.getRoomNumber()); room.setStateFor(session->getAccountInfo().uniqueId, Common::Enums::STATE_WAITING); session->setRoomNumber(room.getRoomNumber()); - const std::pair roomInfo{ room.getRoomNumber() - 1, 1 }; // {roomNum, unk} + const std::pair roomInfo{ room.getRoomNumber() - 1, 2 }; // {roomNum, unk} response.setExtra(RoomCreationExtra::CREATION_SUCCESS); response.setData(reinterpret_cast(&roomInfo), sizeof(roomInfo)); session->asyncWrite(response); // Disable team balance for now, since it causes issues such as team bugs - if (room.isModeTeamBased()) + if (room.isModeTeamBased() && room.getRoomSettings().mode != Common::Enums::AiBattle) { response.setCommand(125, 0, 0, room.getRoomSettings().mode); auto settings = room.getRoomSettingsUpdate(); @@ -93,15 +99,10 @@ namespace Main } roomsManager.addRoom(std::move(room)); } - else - { - response.setExtra(RoomCreationExtra::CREATION_FAIL); - session->asyncWrite(response); - } END_BENCHMARK(handleRoomCreation, session) } } } -#endif \ No newline at end of file +#endif diff --git a/MainServer/include/Handlers/Room/RoomInviteJoin.h b/MainServer/include/Handlers/Room/RoomInviteJoin.h index a834ca1b..ffa76764 100644 --- a/MainServer/include/Handlers/Room/RoomInviteJoin.h +++ b/MainServer/include/Handlers/Room/RoomInviteJoin.h @@ -57,7 +57,7 @@ namespace Main session->sendMessage("You cannot join this friend as you're already in their room."); return; } - else if (targetRoomNum >= Common::Constants::maxPartiesPerClan) + else if (targetRoomNum >= Common::Constants::clanRoomNumberStart) { session->sendMessage("The target user is in a clan room"); return; @@ -146,4 +146,4 @@ namespace Main } } -#endif \ No newline at end of file +#endif diff --git a/MainServer/include/Handlers/Room/RoomJoinHandler.h b/MainServer/include/Handlers/Room/RoomJoinHandler.h index 9256c3c4..b6935129 100644 --- a/MainServer/include/Handlers/Room/RoomJoinHandler.h +++ b/MainServer/include/Handlers/Room/RoomJoinHandler.h @@ -7,6 +7,7 @@ #include "../../Structures/Room/RoomJoinLatestInfo.h" #include #include "../../Structures/Room/RoomJoinLatestInfo.h" +#include namespace Main { @@ -42,6 +43,13 @@ namespace Main { START_BENCHMARK + if (session->getPlayer().getRoomNumber()) + { + session->sendMessage("Error: you are already inside a room (room number: " + std::to_string(session->getPlayer().getRoomNumber()) + + ", cannot join another room!"); + return; + } + Main::ClientData::RoomInfo requestStructure = followUserRoomInfo.value_or(Main::Details::parseData(request)); const bool hasInputtedPassword = request.getDataSize() == 20; @@ -51,6 +59,11 @@ namespace Main if (Main::Classes::Room* room = roomsManager.getRoomByNumber(requestStructure.roomNumber + 1)) { + if (room->getRoomSettings().mode == Common::Enums::AiBattle && !joinInvisible) + { + session->sendMessage("Error: cannot join AI battle mode room"); + return; + } if (room->playerExists(session->getId())) { ::Utils::Logger::log("[handleRoomJoin] Duplicate player detected - disconnected target session", ::Utils::LogType::Warning); diff --git a/MainServer/include/Handlers/Room/RoomMiscHandler.h b/MainServer/include/Handlers/Room/RoomMiscHandler.h index 140c76fa..66a21dc4 100644 --- a/MainServer/include/Handlers/Room/RoomMiscHandler.h +++ b/MainServer/include/Handlers/Room/RoomMiscHandler.h @@ -7,7 +7,7 @@ #include "Network/Packet.h" #include "../Room/RoomJoinHandler.h" #include "../Room/RoomLeaveHandler.h" - +#include namespace Main { @@ -26,6 +26,11 @@ namespace Main { if (Main::Classes::Room* room = roomsManager.getRoomByNumber(session->getPlayer().getRoomNumber())) { + if (room->getRoomSettings().mode == Common::Enums::AiBattle) + { + session->sendMessage("Cannot change settings in AI battle, please create a new room"); + return; + } auto response = request; if (request.getOrder() == 125 && request.getExtra() == 28) @@ -39,10 +44,8 @@ namespace Main } votekickResponse; // targetUniqueId and kickReason is provided by the client - std::memcpy(&votekickResponse.targetUniqueId, reinterpret_cast(const_cast(request.getData())), - sizeof(Main::Structures::UniqueId)); - std::memcpy(&votekickResponse.kickReasonId, reinterpret_cast(const_cast(request.getData() + 4)), - sizeof(std::uint32_t)); + votekickResponse.targetUniqueId = Main::Details::parseData(request); + votekickResponse.kickReasonId = Main::Details::parseData(request, sizeof(Main::Structures::UniqueId)); if (auto targetSession = room->getPlayer(votekickResponse.targetUniqueId)) { @@ -50,6 +53,11 @@ namespace Main { session->sendMessage("Cannot votekick a staff member!"); } + else if (!session->getPlayer().isVotekickEnabled()) + { + session->sendMessage("Your votekicking permissions are currently disabled."); + return; + } else if (session->getAccountInfo().microPoints < 100) { session->sendMessage("Not enough micro points for a votekick!"); @@ -142,9 +150,7 @@ namespace Main if (request.getDataSize() == sizeof(Main::Structures::RoomSettingsUpdateBase)) { // Both title & password changed if (!room->isHost(session->getAccountInfo().uniqueId)) return; - Main::Structures::RoomSettingsUpdateBase updatedRoomSettings; - std::memcpy(&updatedRoomSettings, request.getData(), request.getDataSize()); - + auto updatedRoomSettings = Main::Details::parseData(request); room->updateRoomSettings(updatedRoomSettings, request.getOption()); // Disable team balance @@ -155,8 +161,7 @@ namespace Main else if (request.getDataSize() == sizeof(Main::Structures::RoomSettingsUpdateTitle)) { if (!room->isHost(session->getAccountInfo().uniqueId)) return; - Main::Structures::RoomSettingsUpdateTitle updatedRoomSettings; - std::memcpy(&updatedRoomSettings, request.getData(), request.getDataSize()); + Main::Structures::RoomSettingsUpdateTitle updatedRoomSettings = Main::Details::parseData(request); room->updateRoomSettings(updatedRoomSettings.roomSettingsUpdateBase, request.getOption()); room->updateTitle(updatedRoomSettings.title); @@ -169,8 +174,7 @@ namespace Main else if (request.getDataSize() == sizeof(Main::Structures::RoomSettingsUpdatePassword)) { if (!room->isHost(session->getAccountInfo().uniqueId)) return; - Main::Structures::RoomSettingsUpdatePassword updatedRoomSettings; - std::memcpy(&updatedRoomSettings, request.getData(), request.getDataSize()); + Main::Structures::RoomSettingsUpdatePassword updatedRoomSettings = Main::Details::parseData(request); room->updateRoomSettings(updatedRoomSettings.roomSettingsUpdateBase, request.getOption()); room->updatePassword(updatedRoomSettings.password); @@ -183,8 +187,8 @@ namespace Main else if (request.getDataSize() == sizeof(Main::Structures::RoomSettingsUpdateTitlePassword)) { if (!room->isHost(session->getAccountInfo().uniqueId)) return; - Main::Structures::RoomSettingsUpdateTitlePassword updatedRoomSettings; - std::memcpy(&updatedRoomSettings, request.getData(), request.getDataSize()); + Main::Structures::RoomSettingsUpdateTitlePassword updatedRoomSettings = + Main::Details::parseData(request); room->updateRoomSettings(updatedRoomSettings.roomSettingsUpdateBase, request.getOption()); room->updatePassword(updatedRoomSettings.password); diff --git a/MainServer/include/Handlers/Room/RoomStartHandler.h b/MainServer/include/Handlers/Room/RoomStartHandler.h index fbaa9f45..6faae465 100644 --- a/MainServer/include/Handlers/Room/RoomStartHandler.h +++ b/MainServer/include/Handlers/Room/RoomStartHandler.h @@ -7,6 +7,7 @@ #include "../../Classes/RoomsManager.h" #include "../../Classes/ClanRoom.h" #include "../../Classes/ClansManager.h" +#include namespace Main { @@ -69,8 +70,12 @@ namespace Main } if (room->isHost(selfUniqueId)) { + if (room->getRoomSettings().mode == Common::Enums::BossBattle && room->getAllPlayers().size() > 4) + { + session->sendMessage("Error: Boss battle can only be started when there are 4 players (or less)"); + return; + } if (room->isCsdMode() && !room->isEveryoneCsd()) return; - room->generateMapIfRandom(); if (!Main::Ipc::M2C_sendMapId(selfUniqueId.session, room->getActualMap(), room->getRoomSettings().mode)) @@ -119,31 +124,33 @@ namespace Main clanRooms.second->updatePartyStatus(true); } } + if (room->getRoomSettings().mode == Common::Enums::BossBattle && !session->removeBossBattleTicket()) + { + session->sendMessage("[Handlers::handleRoomStart] Could not find boss battle ticket - if this is an error, report it"); + return; + } room->startMatch(); } else if (room->hasMatchStarted()) { + session->m_totalBossBattleRespawnsLeft = 3; room->setStateFor(selfUniqueId, Common::Enums::PlayerState::STATE_NORMAL); - if (room->isAssassinMode() || (room->getRoomSettings().map == Common::Enums::AcademyTrainingGround - && room->getRoomSettings().mode == Common::Enums::FreeForAll)) + Main::ClientData::PlayerTeamInfo info; + info.uid = selfUniqueId; + std::memcpy(info.nickname, session->getAccountInfo().nickname, 16); + if (auto foundTeam = room->getTeamForSession(selfUniqueId.session)) { - Main::ClientData::PlayerTeamInfo info; - info.uid = selfUniqueId; - std::memcpy(info.nickname, session->getAccountInfo().nickname, 16); - if (auto foundTeam = room->getTeamForSession(selfUniqueId.session)) - { - info.team = *foundTeam; - } - else - { - Utils::Logger::log("[Handlers::StartMatch] Failed to retrieve player team for IPC", Utils::LogType::Error); - return; - } - if (!Main::Ipc::M2C_sendPlayerTeamInfoBatch(session->getId(), { info })) - { - Utils::Logger::log("[Handlers::StartMatch] Failed to send single player team info to Cast Server", Utils::LogType::Error); - } + info.team = *foundTeam; + } + else + { + Utils::Logger::log("[Handlers::StartMatch] Failed to retrieve player team for IPC", Utils::LogType::Error); + return; + } + if (!Main::Ipc::M2C_sendPlayerTeamInfoBatch(session->getId(), { info })) + { + Utils::Logger::log("[Handlers::StartMatch] Failed to send single player team info to Cast Server", Utils::LogType::Error); } } @@ -157,7 +164,7 @@ namespace Main if (room->isHost(selfUniqueId)) { // broadcast the tick to the room const std::uint64_t roomTick = Details::getUtcTimeMs() - timeSinceLastServerRestart; - response.setCommand(258, 0, 1, 0); + response.setCommand(258, 0, 1, 0); // What's extra 5 here? response.setData(reinterpret_cast(&roomTick), sizeof(roomTick)); room->broadcastToRoom(response); } diff --git a/MainServer/include/Handlers/Room/RoomsListHandler.h b/MainServer/include/Handlers/Room/RoomsListHandler.h index 0b1de695..409e7909 100644 --- a/MainServer/include/Handlers/Room/RoomsListHandler.h +++ b/MainServer/include/Handlers/Room/RoomsListHandler.h @@ -6,6 +6,7 @@ #include "../../MainEnums.h" #include "../../Classes/RoomsManager.h" #include +#include namespace Main { diff --git a/MainServer/include/Handlers/Room/SimpleSettingHandler.h b/MainServer/include/Handlers/Room/SimpleSettingHandler.h index e5a1a31e..768dbe35 100644 --- a/MainServer/include/Handlers/Room/SimpleSettingHandler.h +++ b/MainServer/include/Handlers/Room/SimpleSettingHandler.h @@ -17,6 +17,12 @@ namespace Main { if (Main::Classes::Room* room = roomsManager.getRoomByNumber(session->getPlayer().getRoomNumber())) { + if (room->getRoomSettings().mode == Common::Enums::AiBattle) + { + session->sendMessage("Cannot change settings in AI battle, please create a new room"); + return; + } + if (!room->isHost(session->getAccountInfo().uniqueId)) { return; diff --git a/MainServer/include/Handlers/Trade/TradeAckHandler.h b/MainServer/include/Handlers/Trade/TradeAckHandler.h index 92612c3d..4669a78e 100644 --- a/MainServer/include/Handlers/Trade/TradeAckHandler.h +++ b/MainServer/include/Handlers/Trade/TradeAckHandler.h @@ -27,8 +27,7 @@ namespace Main const std::uint32_t now = static_cast(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); if (now >= tradeInfo.startDate && now <= tradeInfo.endDate) { - std::uint32_t targetAccountId; - std::memcpy(&targetAccountId, request.getData() + sizeof(std::uint32_t), sizeof(std::uint32_t)); + const std::uint32_t targetAccountId = Main::Details::parseData(request, sizeof(std::uint32_t)); const auto& selfAccountInfo = session->getAccountInfo(); if (targetAccountId == selfAccountInfo.accountID) { // prevent trading with self diff --git a/MainServer/include/Handlers/Trade/TradeAddItemHandler.h b/MainServer/include/Handlers/Trade/TradeAddItemHandler.h index adbfb0a5..6e9de204 100644 --- a/MainServer/include/Handlers/Trade/TradeAddItemHandler.h +++ b/MainServer/include/Handlers/Trade/TradeAddItemHandler.h @@ -10,6 +10,7 @@ #include #include #include +#include namespace Main { @@ -18,8 +19,7 @@ namespace Main inline void handleAddTradeItem(const Common::Network::Packet& request, std::shared_ptr session, Main::Network::SessionsManager& sessionsManager) { - Main::Structures::ItemSerialInfo itemSerialInfo; - std::memcpy(&itemSerialInfo, request.getData() + 8, sizeof(itemSerialInfo)); + const Main::Structures::ItemSerialInfo itemSerialInfo = Main::Details::parseData(request, 8); Common::Network::Packet response; response.setTcpHeader(request.getSession(), Common::Enums::USER_LARGE_ENCRYPTION); @@ -58,6 +58,11 @@ namespace Main session->asyncWrite(response); return; } + else if (!session->getPlayer().isItemTradeable(itemSerialInfo)) + { // exploit attempt + session->closeSocket(); + return; + } else if (session->addTradedItem(itemID, itemSerialInfo)) { response.setExtra(Enums::TradeSystemExtra::TRADE_SUCCESS); diff --git a/MainServer/include/Handlers/Trade/TradeFinalizeHandler.h b/MainServer/include/Handlers/Trade/TradeFinalizeHandler.h index b5761f57..ea2977ab 100644 --- a/MainServer/include/Handlers/Trade/TradeFinalizeHandler.h +++ b/MainServer/include/Handlers/Trade/TradeFinalizeHandler.h @@ -10,14 +10,14 @@ #include #include #include "../../Structures/TradeSystem/TradeAck.h" - +#include "Macros.h" namespace Main { namespace Handlers { -#pragma pack(push) - struct TradeUnusedFinalItem +PACK_PUSH(1) +struct TradeUnusedFinalItem { char unused[8]{}; std::uint32_t totalNewMp{}; // This was used in the old trade system @@ -26,9 +26,8 @@ namespace Main std::uint32_t unusedTotal1{}; std::array itemIdsUnused{}; }; -#pragma pack(pop) +PACK_POP() - // Currently: Both players need to relog to see their newly obtained items. However, after a trade their traded items are directly removed from their inventory. inline void handleTradeFinalization(const Common::Network::Packet& request, std::shared_ptr session, Main::Network::SessionsManager& sessionsManager) { @@ -48,6 +47,20 @@ namespace Main auto selfTradedItems = session->getTradedItems(); auto targetTradedItems = targetSession->getTradedItems(); + + if (!targetSession->getPlayer().hasEnoughInventorySpace(selfTradedItems.size())) + { + session->sendMessage("Error: the other player does not have enough inventory space. Cannot proceed!"); + targetSession->sendMessage("Error: you do not have enough inventory space. Cannot proceed!"); + return; + } + else if (!session->getPlayer().hasEnoughInventorySpace(targetTradedItems.size())) + { + targetSession->sendMessage("Error: the other player does not have enough inventory space. Cannot proceed!"); + session->sendMessage("Error: you do not have enough inventory space. Cannot proceed!"); + return; + } + std::uint64_t targetLatestItemNumber = targetSession->getPlayer().getLatestItemNumber(); std::uint64_t selfLatestItemNumber = session->getPlayer().getLatestItemNumber(); @@ -78,6 +91,7 @@ namespace Main session->deleteItems(session->getTradedItems(), "Item deleted after it was traded to " + std::string{targetSession->getPlayer().getPlayerName()} + " (target AID: " + std::to_string(targetSession->getAccountInfo().accountID) + ")"); session->resetTradeInfo(); + targetSession->deleteItems(targetSession->getTradedItems(), "Item deleted after it was traded to " + std::string{ session->getPlayer().getPlayerName() } + " (target AID: " + std::to_string(session->getAccountInfo().accountID) + ")"); targetSession->spawnItems(selfTradedItems, "Item received from trade, from user " + std::string{ session->getPlayer().getPlayerName() } diff --git a/MainServer/include/Handlers/Trade/TradeInitializationHandler.h b/MainServer/include/Handlers/Trade/TradeInitializationHandler.h index 82a8102c..224b2789 100644 --- a/MainServer/include/Handlers/Trade/TradeInitializationHandler.h +++ b/MainServer/include/Handlers/Trade/TradeInitializationHandler.h @@ -54,8 +54,7 @@ namespace Main response.setOrder(request.getOrder()); response.setExtra(request.getExtra()); - std::uint32_t targetAccountId; - std::memcpy(&targetAccountId, request.getData() + sizeof(std::uint32_t), sizeof(std::uint32_t)); + const std::uint32_t targetAccountId = Main::Details::parseData(request, sizeof(std::uint32_t)); if (auto targetSession = sessionsManager.getSessionByAccountId(targetAccountId)) { diff --git a/MainServer/include/Handlers/Trade/TradeRemoveItemHandler.h b/MainServer/include/Handlers/Trade/TradeRemoveItemHandler.h index af425783..f63cc907 100644 --- a/MainServer/include/Handlers/Trade/TradeRemoveItemHandler.h +++ b/MainServer/include/Handlers/Trade/TradeRemoveItemHandler.h @@ -20,8 +20,7 @@ namespace Main { if (session->hasBeenMatchBanned()) return; - Main::Structures::ItemSerialInfo itemSerialInfo; - std::memcpy(&itemSerialInfo, request.getData() + 8, sizeof(itemSerialInfo)); + const Main::Structures::ItemSerialInfo itemSerialInfo = Main::Details::parseData(request, 8); Common::Network::Packet response; response.setTcpHeader(request.getSession(), Common::Enums::USER_LARGE_ENCRYPTION); diff --git a/MainServer/include/MainEnums.h b/MainServer/include/MainEnums.h index eb0c689f..b2669a6e 100644 --- a/MainServer/include/MainEnums.h +++ b/MainServer/include/MainEnums.h @@ -302,10 +302,10 @@ namespace Main enum ItemExpirationType { - // // 0=nothing happens, 1=becomes unused(1 day); 2=bomb?!; 3<=expired UNLIMITED = 0, UNUSED, - BOMB + BOMB, + EXPIRED // >= 3 }; enum ItemFrom diff --git a/MainServer/include/MainServer.h b/MainServer/include/MainServer.h index 2b3f09fb..24d38f84 100644 --- a/MainServer/include/MainServer.h +++ b/MainServer/include/MainServer.h @@ -16,6 +16,7 @@ #include #include +#include namespace Main { @@ -42,6 +43,7 @@ namespace Main Main::Classes::RoomsManager m_roomsManager; Main::Classes::ClansManager m_clansManager; Main::Command::ChatCommands m_chatCommands; + Main::Classes::ReportManager m_reportManager; std::unordered_map)>> m_generalItemCallbacks; std::unordered_map)>> m_cashItemsCallbacks; std::unordered_map, const Main::Structures::ItemSerialInfo&, @@ -58,7 +60,7 @@ namespace Main // Events Main::Structures::EventMissionInfo m_eventMissionInfo; Main::Structures::EventMissionInfo m_tradeSystemEvent; - Main::Structures::CapsuleListDatabase m_capsuleListDb; + Main::Structures::CapsuleListDatabase m_capsuleSaleEvent; Main::Structures::ExpMpBonusInfo m_expMpEvent; // Ac @@ -73,6 +75,8 @@ namespace Main constexpr void setServerOffline(bool v) { m_isServerOffline = v; } constexpr void setRoomCreationTo(bool v) { m_roomCreationEnabled = v; } constexpr bool getRoomCreation() const noexcept { return m_roomCreationEnabled; }; + Main::Classes::ReportManager& getReportManager() { return m_reportManager; } + }; } diff --git a/MainServer/include/Network/HttpSession.h b/MainServer/include/Network/HttpSession.h index 91b72f0a..0acf8bff 100644 --- a/MainServer/include/Network/HttpSession.h +++ b/MainServer/include/Network/HttpSession.h @@ -8,6 +8,7 @@ #include "boost/beast/ssl/ssl_stream.hpp" #include #include "jwt-cpp/jwt.h" +#include namespace Main { diff --git a/MainServer/include/Network/MainSession.h b/MainServer/include/Network/MainSession.h index dfed211c..faa2ff91 100644 --- a/MainServer/include/Network/MainSession.h +++ b/MainServer/include/Network/MainSession.h @@ -35,13 +35,16 @@ namespace Main Main::Persistence::MainScheduler& m_scheduler; Common::Network::Packet m_packet{}; Ac::AntiCheatManager& m_acManager; - std::mt19937 m_gen{ std::random_device{}() }; std::uniform_int_distribution m_dist{ 1, 100 }; std::unordered_map m_eventMissions; + std::unordered_set m_packetReplicaWhitelist{ 71, 81, 86, 87, 101, 284 }; + + bool spawnCouponCommon(const std::uint32_t total, bool useAddItem); public: + std::uint16_t m_totalBossBattleRespawnsLeft = 3; std::uint64_t m_matchStartTime{}; bool m_hasCheckedMatchBan = false; bool m_isInvisible{}; @@ -49,7 +52,6 @@ namespace Main std::string m_hwid{ "" }; std::uint32_t m_pingPacketCounter = 0; - public: using Item = Main::Structures::Item; using EquippedItem = Main::Structures::EquippedItem; using DetailedEquippedItem = Main::Structures::DetailedEquippedItem; @@ -95,6 +97,24 @@ namespace Main } } + template + bool checkEquippedItems(Predicate&& pred, const std::string& failMessage) + { + const auto equippedItems = m_player.getEquippedItemsFor(m_player.getAccountInfo().latestSelectedCharacter); + + for (const auto& equippedItem : equippedItems) + { + if (equippedItem.serialInfo.itemNumber == 0) + continue; + + if (!pred(static_cast(equippedItem.type), equippedItem.id)) + { + sendMessage("Item Type " + std::to_string(equippedItem.type) + " " + failMessage); + return false; + } + } + return true; + } public: template @@ -196,6 +216,8 @@ namespace Main void acceptFriendRequest(std::shared_ptr senderSession, const Main::Structures::Friend& target, const std::uint8_t* const data); + bool removeBossBattleTicket(); + private: void handleOfflineFriendRequest(const AccountInfo& accountInfo, const char* nickname); void handleOnlineFriendRequest(std::shared_ptr targetSession, const AccountInfo& accountInfo); @@ -234,8 +256,8 @@ namespace Main bool prolongItems(const std::vector& toProlongItems, const std::vector& newExpirations); - void upgradeWeapon(std::uint32_t itemId, const Main::Structures::ItemSerialInfo& serialInfo, std::uint32_t mpNeeded, bool hasParent, - std::uint8_t mission, std::uint8_t option, bool, bool); + bool upgradeWeapon(std::uint32_t itemId, const Main::Structures::ItemSerialInfo& serialInfo, bool hasParent, + std::uint8_t mission, std::uint8_t option, bool useEnergyRefund, bool useGlue); void resetUpgrade(const Main::ClientData::UpgradeReset& upgradeReset); @@ -255,8 +277,6 @@ namespace Main bool addItem(const Main::Structures::Giftbox& item); - bool addItemPacket(const Item& item); - bool addItems(const std::vector& boughtItems, bool areCouponItems = false); bool setAccountRockTotens(std::uint32_t rt); @@ -271,7 +291,7 @@ namespace Main bool setExperience(std::uint32_t experience); - void setPlayerName(const std::string& playerName); + bool setPlayerName(const std::string& playerName); void addEnergyToItem(const Main::ClientData::ItemAddEnergy& itemAddEnergy, std::uint16_t option, std::uint16_t mission); @@ -289,11 +309,11 @@ namespace Main void equipItem(const std::uint16_t itemNumber); - void replaceItem(const Main::Structures::ItemSerialInfo& serialInfo, std::uint32_t newItemId, const std::string& action); + bool replaceItem(const Main::Structures::ItemSerialInfo& serialInfo, std::uint32_t newItemId, const std::string& action); bool spawnItemCommand(std::uint32_t itemId, const std::string& action); - - bool spawnCoupon(std::uint32_t itemId); + bool spawnCoupon(const std::uint32_t total); + bool spawnCouponImmediate(const std::uint32_t total); bool receiveGift(std::uint32_t itemId, const std::string& giftDescription); @@ -304,11 +324,16 @@ namespace Main bool sendDeletePacket(const Main::Structures::ItemSerialInfo& itemSerialInfoToDelete); void sendCurrency(); + void sendCurrency(std::uint32_t newMP, std::uint32_t newRT); void sendMp(std::uint32_t mptoAdd); void sendRt(std::uint32_t rtToAdd); + void reduceEquippedItemsDurability(std::uint32_t weaponRestriction); + + void updateItemDurability(std::uint32_t itemNumber, std::uint32_t newDurability); + void switchItemEquip(std::uint32_t characterId, std::uint64_t itemNumber); void unequipItem(std::uint64_t itemType); @@ -329,8 +354,14 @@ namespace Main bool enableRoomCreation(); + bool disableVotekick(std::uint64_t daysDuration); + + bool enableVotekick(); + void setRoomCreationDisabled(); + void setVotekickDisabled(); + void setMute(Main::Structures::MuteInfo val); void sendLobbyList(const std::vector>& allSessions); @@ -406,10 +437,13 @@ namespace Main void resetTradeInfo(); - void spawnItem(std::uint32_t itemId, const Main::Structures::ItemSerialInfo& itemSerialInfo, const std::string& action); + bool spawnItem(std::uint32_t itemId, const Main::Structures::ItemSerialInfo& itemSerialInfo, const std::string& action); bool hasCsdItems(); + bool hasBasicItems(); + + void respawnBossBattle(); }; } } -#endif \ No newline at end of file +#endif diff --git a/MainServer/include/Persistence/MainDatabaseManager.h b/MainServer/include/Persistence/MainDatabaseManager.h index 5d2dcf24..cd34178b 100644 --- a/MainServer/include/Persistence/MainDatabaseManager.h +++ b/MainServer/include/Persistence/MainDatabaseManager.h @@ -2,7 +2,7 @@ #define MAIN_DATABASE_MANAGER_H #include - +#include #include "../MainEnums.h" #include "Utils/SetupParser.h" #include "Utils/Logger.h" @@ -56,6 +56,10 @@ namespace Main std::optional getBanInfoByNickname(const std::string& nickname); std::optional getMuteInfoByNickname(const std::string& nickname); bool isRoomCreationDisabled(std::uint32_t playerID); + bool isVotekickDisabled(std::uint32_t playerID); + std::optional getVotekickDisabledUntil(const std::string& nickname); + bool updateVotekickDisabledUntil(const std::string& nickname, const std::string& until); + bool resetVotekickDisabledUntil(const std::string& nickname); bool unbanPlayer(const std::string& nickname); bool addPlayer(const std::string& username, const std::string& password, const std::string& nickname); auto getPlayerItems(std::uint32_t playerID) -> std::pair, std::unordered_map>>; @@ -178,6 +182,8 @@ namespace Main return getRewards("MonthlyRewards", "32", rewardsGenerator, l, l2); } + void reduceDurability(std::uint32_t accountId, const std::vector& equippedItems); + void updateItemDurability(std::uint32_t accountId, std::uint32_t itemNumber, std::uint32_t newDurability); void updateBattery(std::uint32_t accountId, std::uint32_t newBattery); void updatePlayerStats(std::uint32_t accountId, const Main::Structures::AccountInfo& updatedAccountInfo); void updateClanContribution(std::uint32_t clanId, std::uint32_t contribution); @@ -263,4 +269,4 @@ namespace Main } } -#endif \ No newline at end of file +#endif diff --git a/MainServer/include/Structures/AccountInfo/MainAccountAchievements.h b/MainServer/include/Structures/AccountInfo/MainAccountAchievements.h index 4aecc7b7..b5683065 100644 --- a/MainServer/include/Structures/AccountInfo/MainAccountAchievements.h +++ b/MainServer/include/Structures/AccountInfo/MainAccountAchievements.h @@ -4,12 +4,13 @@ #include "Enums/AchievementEnums.h" #include "Utils/Logger.h" #include +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct AccountAchievements { // MVS had probably only achievementsTier1 @@ -23,8 +24,8 @@ namespace Main achievementsTier1 |= (static_cast(1) << achievementIdx); } }; -#pragma pack(pop) - } +PACK_POP() + } } #endif \ No newline at end of file diff --git a/MainServer/include/Structures/AccountInfo/MainAccountInfo.h b/MainServer/include/Structures/AccountInfo/MainAccountInfo.h index 9bb8e49a..575e088a 100644 --- a/MainServer/include/Structures/AccountInfo/MainAccountInfo.h +++ b/MainServer/include/Structures/AccountInfo/MainAccountInfo.h @@ -7,13 +7,13 @@ #include "Enums/MiscellaneousEnums.h" #include "MainAccountUniqueId.h" #include "MainAccountAchievements.h" - +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct AccountInfo { std::uint64_t diorama1 : 23 = 0; @@ -98,8 +98,7 @@ namespace Main } }; -#pragma pack(pop) - +PACK_POP() } } diff --git a/MainServer/include/Structures/AccountInfo/MainAccountUniqueId.h b/MainServer/include/Structures/AccountInfo/MainAccountUniqueId.h index d1373cb8..90600e2f 100644 --- a/MainServer/include/Structures/AccountInfo/MainAccountUniqueId.h +++ b/MainServer/include/Structures/AccountInfo/MainAccountUniqueId.h @@ -2,13 +2,13 @@ #define MAIN_ACOUNT_UNIQUE_ID_H #include - +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct UniqueId { std::uint32_t session : 16 = 0; @@ -17,7 +17,7 @@ namespace Main bool operator==(const UniqueId& other) const = default; }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/AccountInfo/MainLobbyAccountInfo.h b/MainServer/include/Structures/AccountInfo/MainLobbyAccountInfo.h index 129f9cc9..ee061951 100644 --- a/MainServer/include/Structures/AccountInfo/MainLobbyAccountInfo.h +++ b/MainServer/include/Structures/AccountInfo/MainLobbyAccountInfo.h @@ -6,13 +6,14 @@ #include #include "MainAccountAchievements.h" #include "MainAccountInfo.h" - +#include "Macros.h" +#include namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct LobbyAccountInfo { std::uint64_t dioramaInfo{}; @@ -71,7 +72,7 @@ namespace Main std::memcpy(clanName, accountInfo.clanName, 16); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/Capsule/CapsuleList.h b/MainServer/include/Structures/Capsule/CapsuleList.h index cc9b6761..c3ca20b2 100644 --- a/MainServer/include/Structures/Capsule/CapsuleList.h +++ b/MainServer/include/Structures/Capsule/CapsuleList.h @@ -2,22 +2,24 @@ #define CAPSULE_LIST_STRUCT_H #include +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct CapsuleList { - std::uint32_t capsuleInfoId{}; - std::uint32_t newPrice{}; + std::uint32_t capsuleInfoId = 0; + std::uint32_t newPrice : 21 = 0; + std::uint32_t unknown2 : 11 = 0; std::uint32_t saleEventStartDate{}; - std::uint32_t saleEventEndDate{}; + std::uint32_t saleEventEndDate{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct CapsuleListDatabase { std::uint32_t newRtPrice{}; @@ -25,17 +27,17 @@ namespace Main std::uint32_t saleEventStartDate{}; std::uint32_t saleEventEndDate{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct EventMissionInfo { std::uint32_t startDate{}; std::uint32_t endDate{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct ExpMpBonusInfo { std::uint32_t startDate{}; @@ -43,7 +45,7 @@ namespace Main std::uint32_t expBonusPercent{}; std::uint32_t mpBonusPercent{}; }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/Capsule/CapsuleSpin.h b/MainServer/include/Structures/Capsule/CapsuleSpin.h index 3b77ef54..9fb4e296 100644 --- a/MainServer/include/Structures/Capsule/CapsuleSpin.h +++ b/MainServer/include/Structures/Capsule/CapsuleSpin.h @@ -5,12 +5,14 @@ #include "../Item/MainItemSerialInfo.h" #include "../../MainEnums.h" #include "../../Detail/CdbUtils.h" +#include "Macros.h" + namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct CapsuleSpin { Main::Structures::ItemId itemId; @@ -25,7 +27,7 @@ namespace Main itemSerialInfo.itemOrigin = Main::Enums::ItemFrom::SHOP; } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/Clan/ClanStructures.h b/MainServer/include/Structures/Clan/ClanStructures.h index d8fcc1d4..e119cf05 100644 --- a/MainServer/include/Structures/Clan/ClanStructures.h +++ b/MainServer/include/Structures/Clan/ClanStructures.h @@ -4,12 +4,14 @@ #include #include "../AccountInfo/MainAccountUniqueId.h" #include "../ClientData/Structures.h" +#include "Macros.h" +#include namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) // This structure is used when one clicks on "Clan Match" (= info about clan rooms of the target player's clan) struct PartyInfo { @@ -24,10 +26,10 @@ namespace Main std::uint32_t unknown2 : 12 = 0; char leaderName[16]{}; }; -#pragma pack(pop) +PACK_POP() // Single info of a player who's waiting in a clan room -#pragma pack(push,1) +PACK_PUSH(1) struct PartyPlayerInfo { Main::Structures::UniqueId uid{}; @@ -40,9 +42,9 @@ namespace Main std::uint64_t padding : 4 = 0; char nickname[16]{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct ClanRoomSettings { std::uint32_t mode : 5 = 0; // 19 = bomb battle, 18 = tdm, 17 = eli, 16 = CTB @@ -60,10 +62,10 @@ namespace Main { } }; -#pragma pack(pop) +PACK_POP() // Used to send the info of the player who joined the clan match to the other players waiting -#pragma pack(push, 1) +PACK_PUSH(1) struct JoinPartyInfo { char nickname[16]{}; @@ -85,10 +87,9 @@ namespace Main std::memcpy(this->nickname, nickname, Common::Constants::maxNicknameSize); } }; -#pragma pack(pop) +PACK_POP() - -#pragma pack (push, 1) +PACK_PUSH(1) struct RegisteredClanInfo { std::uint64_t clanRoomId : 16 = 0; @@ -125,8 +126,7 @@ namespace Main std::memcpy(leaderName, initLeaderName, Common::Constants::maxNicknameSize); } }; - -#pragma pack (pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/ClientData/Structures.h b/MainServer/include/Structures/ClientData/Structures.h index dc5692a6..8b139d37 100644 --- a/MainServer/include/Structures/ClientData/Structures.h +++ b/MainServer/include/Structures/ClientData/Structures.h @@ -3,6 +3,7 @@ #include #include "../Item/MainItemSerialInfo.h" +#include "Macros.h" // NOTE: // To correctly "read" client data: @@ -14,83 +15,83 @@ namespace Main { namespace ClientData { -#pragma pack(push, 1) +PACK_PUSH(1) struct Ping { std::uint32_t unknown : 10 = 0; std::uint32_t ping : 10 = 0; std::uint32_t rest : 12 = 0; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct ItemRefund { Main::Structures::ItemSerialInfo serialInfo{}; std::uint32_t mpToAdd{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct ItemAddEnergy { Main::Structures::ItemSerialInfo serialInfo{}; std::uint32_t usedEnergy{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct MailboxMessage { char nickname[16]{}; char message[256]{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct RoomInfo { std::uint16_t roomNumber{}; std::uint16_t unknown{ 2 }; // seemingly always 2 for some reason char password[9]{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct ClanRoomInfo { std::uint16_t clanId{}; std::uint16_t roomNumber{}; char password[9]{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct BoxOpen { Main::Structures::ItemSerialInfo serialInfo; Main::Structures::ItemSerialInfo serialInfo2; // e.g. capsule }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct UpgradeReset { Main::Structures::ItemSerialInfo weaponToResetSerialInfo; Main::Structures::ItemSerialInfo upgradeResetItemSerialInfo; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct CapsuleSpin { std::uint32_t capsuleId{}; std::uint32_t currencySpent{}; std::uint64_t unknown{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct ClientEndingMatchHeader { std::uint32_t redScore : 8 = 0; @@ -98,18 +99,18 @@ namespace Main std::uint32_t unknown1 : 8 = 0; // Seems related to num of players std::uint32_t unknown2 : 8 = 0; // Seems related to num of players }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct SinglewaveEndRequest { std::uint32_t type; std::uint32_t score; std::uint32_t stage; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct ClanRoomSettings { std::uint16_t unknown1 : 4 = 0; @@ -117,9 +118,9 @@ namespace Main std::uint16_t map : 7 = 0; std::uint16_t unknown = 0; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct ClientVersion { std::uint32_t ver4 : 8 = 0; @@ -132,9 +133,9 @@ namespace Main return ver2 == v1 && ver3 == v2 && ver4 == v3; } }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct ClientAuthorization { std::uint32_t accountID; @@ -142,7 +143,7 @@ namespace Main std::uint32_t localIp; ClientVersion clientVersion; }; -#pragma pack(pop) +PACK_POP() struct EventMissionPoint { @@ -165,15 +166,31 @@ namespace Main Main::Structures::ItemSerialInfo serialInfo; }; -#pragma pack(push, 1) +PACK_PUSH(1) struct PlayerTeamInfo { Main::Structures::UniqueId uid; std::uint32_t team; char nickname[16]{}; }; -#pragma pack(pop) +PACK_POP() +PACK_PUSH(1) + struct ItemRepair + { + std::uint32_t newTotalRT{}; + std::uint32_t newTotalMP{}; + std::vector serialInfo; + }; +PACK_POP() + +PACK_PUSH(1) + struct SingleWeaponDurabilityDamage + { + Main::Structures::ItemSerialInfo serialInfo{}; + std::uint32_t durabilityToRemove; + }; +PACK_POP() } } #endif \ No newline at end of file diff --git a/MainServer/include/Structures/EndScoreboard.h b/MainServer/include/Structures/EndScoreboard.h index 01e9628b..f97a4f3d 100644 --- a/MainServer/include/Structures/EndScoreboard.h +++ b/MainServer/include/Structures/EndScoreboard.h @@ -3,13 +3,14 @@ #include #include "AccountInfo/MainAccountUniqueId.h" +#include "Macros.h" namespace Main { // Todo: Remove assists, recheck this! namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct ClientEndingMatch { std::uint32_t meleeKills : 8 = 0; // total infections for zombie mode @@ -32,10 +33,9 @@ namespace Main std::uint32_t u3 : 8 = 0; Main::Structures::UniqueId uniqueId{}; }; -#pragma pack(pop) +PACK_POP() - -#pragma pack(push, 1) +PACK_PUSH(1) struct ScoreboardResponse { std::uint32_t meleeKills : 8; @@ -64,6 +64,8 @@ namespace Main { } + ScoreboardResponse() = default; + std::array weaponKills() const { return @@ -78,8 +80,7 @@ namespace Main }; } }; -#pragma pack(pop) - +PACK_POP() } } diff --git a/MainServer/include/Structures/Item/MainBoughtItem.h b/MainServer/include/Structures/Item/MainBoughtItem.h index 757b83d7..31cbd651 100644 --- a/MainServer/include/Structures/Item/MainBoughtItem.h +++ b/MainServer/include/Structures/Item/MainBoughtItem.h @@ -10,12 +10,14 @@ #include "ConstantDatabase/Structures/CdbItemInfo.h" #include "ConstantDatabase/Structures/CdbWeaponsInfo.h" #include "ItemId.h" +#include "Macros.h" + namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct BoughtItem { Main::Structures::ItemId itemId; @@ -38,9 +40,9 @@ namespace Main BoughtItem() = default; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct BoughtItemToProlong { ItemSerialInfo serialInfo{}; @@ -52,7 +54,7 @@ namespace Main { } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/Item/MainEquippedItem.h b/MainServer/include/Structures/Item/MainEquippedItem.h index acaf1deb..5ee53720 100644 --- a/MainServer/include/Structures/Item/MainEquippedItem.h +++ b/MainServer/include/Structures/Item/MainEquippedItem.h @@ -1,23 +1,29 @@ - #ifndef MAIN_EQUIPPED_ITEM_INFO_H #define MAIN_EQUIPPED_ITEM_INFO_H -#include #include -#include "MainItemSerialInfo.h" #include +#include "MainItemSerialInfo.h" +#include "Macros.h" namespace Main { namespace Structures { class Item; -#pragma pack(push, 1) + +#ifdef _WIN32 + using ExpirationTimeType = __time32_t; +#else + using ExpirationTimeType = std::int32_t; +#endif + +PACK_PUSH(1) struct EquippedItem { std::uint32_t type : 9 = 0; std::uint32_t id : 23 = 0; - __time32_t expirationDate{}; + ExpirationTimeType expirationDate{}; ItemSerialInfo serialInfo{}; std::uint16_t durability{}; std::uint16_t energy{}; @@ -31,10 +37,9 @@ namespace Main //EquippedItem& operator=(const Item& other); //EquippedItem& operator=(const EquippedItem& other); }; -#pragma pack(pop) - +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct DetailedEquippedItem : EquippedItem { std::uint16_t characterId{}; @@ -51,9 +56,9 @@ namespace Main { } }; -#pragma pack(pop) - -#pragma pack(push, 1) +PACK_POP() + +PACK_PUSH(1) struct BasicEquippedItem { private: @@ -74,8 +79,9 @@ namespace Main public: std::array items{}; }; -#pragma pack(pop) +PACK_POP() +PACK_PUSH(1) struct BasicEquippedItemLobby { private: @@ -95,8 +101,9 @@ namespace Main public: std::array items{}; }; -#pragma pack(pop) +PACK_POP() } } #endif + diff --git a/MainServer/include/Structures/Item/MainItem.h b/MainServer/include/Structures/Item/MainItem.h index 0f47b588..4c322ec4 100644 --- a/MainServer/include/Structures/Item/MainItem.h +++ b/MainServer/include/Structures/Item/MainItem.h @@ -1,7 +1,6 @@ #ifndef MAIN_ITEM_INFO_H #define MAIN_ITEM_INFO_H -#include #include #include "MainItemSerialInfo.h" #include "SpawnedItem.h" @@ -11,17 +10,22 @@ #include "../../Detail/CdbUtils.h" #include "../Mailbox.h" #include "../TradeSystem/TradeSystemItem.h" +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct Item { // note: If itemnumber = 0 AND creationDate = 0 ==> basic item Main::Structures::ItemId itemId; - __time32_t expirationDate{}; +#ifdef _WIN32 + __time32_t expirationDate{}; +#else + std::int32_t expirationDate{}; +#endif ItemSerialInfo serialInfo{}; std::uint16_t durability{}; std::uint16_t energy{}; @@ -39,7 +43,11 @@ namespace Main explicit Item(const Main::Structures::CapsuleSpin& capsuleItem) : itemId{ capsuleItem.itemId }, serialInfo{ capsuleItem.itemSerialInfo } , durability{ Main::CdbUtils::getItemDurability(capsuleItem.itemId.itemId ).value_or(0) } - , expirationDate{ static_cast<__time32_t>(capsuleItem.expirationDate) } +#ifdef _WIN32 + , expirationDate{ static_cast<__time32_t>(capsuleItem.expirationDate) } +#else + , expirationDate{ static_cast(capsuleItem.expirationDate) } +#endif { serialInfo.itemOrigin = Main::Enums::ItemFrom::SHOP; } @@ -54,16 +62,25 @@ namespace Main } /*explicit removed on purpose*/ Item(const Main::Structures::EquippedItem& equippedItem) - : itemId{ equippedItem.id }, serialInfo{ equippedItem.serialInfo }, expirationDate{ equippedItem.expirationDate } + : itemId{ equippedItem.id }, serialInfo{ equippedItem.serialInfo } , durability{ equippedItem.durability }, energy{equippedItem.energy}, isSealed { equippedItem.isSealed }, sealLevel{equippedItem.sealLevel} , experienceEnhancement{ equippedItem.experienceEnhancement }, mpEnhancement{ equippedItem.mpEnhancement } +#ifdef _WIN32 + , expirationDate{ static_cast<__time32_t>(equippedItem.expirationDate) } +#else + , expirationDate{ static_cast(equippedItem.expirationDate) } +#endif { } Item(const Main::Structures::SpawnedItem& spawnedItem) : itemId{ spawnedItem.itemId }, serialInfo{ spawnedItem.serialInfo } - , expirationDate{ spawnedItem.expirationDate } , durability{ Main::CdbUtils::getItemDurability(spawnedItem.itemId.itemId).value_or(0) } +#ifdef _WIN32 + , expirationDate{ static_cast<__time32_t>(spawnedItem.expirationDate ) } +#else + , expirationDate{ static_cast(spawnedItem.expirationDate ) } +#endif { serialInfo.itemOrigin = Main::Enums::ItemFrom::SHOP; itemId.stock = spawnedItem.itemId.stock; @@ -94,13 +111,13 @@ namespace Main serialInfo.itemOrigin = 8;// Main::Enums::ItemFrom::SHOP; } }; -#pragma pack(pop) +PACK_POP() struct ItemLogInfo { std::uint64_t itemNumber{}; std::uint64_t itemId; - std::uint32_t expirationDate{}; + std::int64_t expirationDate{}; std::string action; }; } diff --git a/MainServer/include/Structures/Item/MainItemSerialInfo.h b/MainServer/include/Structures/Item/MainItemSerialInfo.h index efae1fac..55bc42b2 100644 --- a/MainServer/include/Structures/Item/MainItemSerialInfo.h +++ b/MainServer/include/Structures/Item/MainItemSerialInfo.h @@ -4,6 +4,7 @@ #include #include #include +#include "Macros.h" #ifdef WIN32 #include @@ -15,7 +16,7 @@ namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct ItemSerialInfo { std::uint64_t itemNumber : 20 = 0; // Note: "0" is treated as a sentinel value for EquippedItem (no item exists with itemNumber = 0 in the database!) @@ -32,7 +33,7 @@ namespace Main itemCreationDate = static_cast<__time32_t>(std::time(0)); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/Item/SpawnedItem.h b/MainServer/include/Structures/Item/SpawnedItem.h index 4a02c5c2..6cf87646 100644 --- a/MainServer/include/Structures/Item/SpawnedItem.h +++ b/MainServer/include/Structures/Item/SpawnedItem.h @@ -11,13 +11,14 @@ #include "ConstantDatabase/Structures/CdbWeaponsInfo.h" #include "../../Detail/CdbUtils.h" #include "ItemId.h" +#include "Macros.h" // Note: Creation date cannot be 0 (otherwise the client doesn't know how to handle equipping/unequipping) !!! namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct SpawnedItem { Main::Structures::ItemId itemId; @@ -32,10 +33,19 @@ namespace Main expirationDate = duration <= 3 ? duration : serialInfo.itemCreationDate + duration; serialInfo.itemOrigin = Main::Enums::ItemFrom::SHOP; } + + explicit SpawnedItem(std::uint32_t id, std::uint32_t stock) + : itemId{ id } + { + itemId.stock = stock; + const std::uint32_t duration = Main::CdbUtils::getItemDuration(id); + expirationDate = duration <= 3 ? duration : serialInfo.itemCreationDate + duration; + serialInfo.itemOrigin = Main::Enums::ItemFrom::SHOP; + } }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct BoxItem { Main::Structures::ItemId itemId; @@ -55,9 +65,9 @@ namespace Main { } }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct GiftItem { std::uint64_t clientData{}; // includes uint32_t unknown, uint32_t timestamp => client uses this to delete the gift from the list of gifts once opened @@ -73,9 +83,9 @@ namespace Main serialInfo.itemOrigin = Main::Enums::ItemFrom::SHOP; } }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct WeeklyReward { std::uint64_t unknown : 16 = 0; @@ -89,9 +99,9 @@ namespace Main day = std::chrono::weekday{ std::chrono::floor(std::chrono::current_zone()->to_local(std::chrono::system_clock::now())) }.iso_encoding(); } }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct MonthlyReward { std::uint64_t month : 16 = 0; @@ -108,7 +118,7 @@ namespace Main day = static_cast(unsigned{ ymd.day() }); } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/Mailbox.h b/MainServer/include/Structures/Mailbox.h index b1a348b4..4fc9cbed 100644 --- a/MainServer/include/Structures/Mailbox.h +++ b/MainServer/include/Structures/Mailbox.h @@ -2,13 +2,13 @@ #define MAILBOX_STRUCTURE_H #include "AccountInfo/MainAccountUniqueId.h" - +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct Mailbox { std::uint32_t accountId{}; @@ -17,9 +17,9 @@ namespace Main char nickname[16]{}; char message[256]{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct Giftbox { std::uint32_t accountId{}; @@ -30,9 +30,9 @@ namespace Main char nickname[16]{}; char message[256]{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct Giftbox2 { std::uint32_t accountId{}; @@ -48,7 +48,7 @@ namespace Main serialInfo.itemOrigin = 8; // Main::Enums::ItemFrom::GIFT; } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/MainEventsList.h b/MainServer/include/Structures/MainEventsList.h index 6c655cab..b6a16e71 100644 --- a/MainServer/include/Structures/MainEventsList.h +++ b/MainServer/include/Structures/MainEventsList.h @@ -5,30 +5,29 @@ #include "AccountInfo/MainAccountUniqueId.h" #include "Enums/RoomEnums.h" #include +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct SingleModeEvent { Common::Enums::GameModes gameMode{}; __time32_t startDate{}; __time32_t endDate{}; }; -#pragma pack(pop) +PACK_POP() - - -#pragma pack(push, 1) +PACK_PUSH(1) struct SingleMapEvent { Common::Enums::GameMaps gameMap{}; __time32_t startDate{}; __time32_t endDate{}; }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/PlayerLists/BlockedPlayer.h b/MainServer/include/Structures/PlayerLists/BlockedPlayer.h index 0bd35189..7dc00e95 100644 --- a/MainServer/include/Structures/PlayerLists/BlockedPlayer.h +++ b/MainServer/include/Structures/PlayerLists/BlockedPlayer.h @@ -2,18 +2,19 @@ #define BLOCKED_PLAYER_STRUCTURE_H #include +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct BlockedPlayer { std::uint32_t targetAccountId{ static_cast(-1) }; // sentinel char targetNickname[16]; }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/PlayerLists/Friend.h b/MainServer/include/Structures/PlayerLists/Friend.h index c941bfde..58ab9d5b 100644 --- a/MainServer/include/Structures/PlayerLists/Friend.h +++ b/MainServer/include/Structures/PlayerLists/Friend.h @@ -5,12 +5,13 @@ #include #include "../AccountInfo/MainAccountUniqueId.h" #include +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct Friend { UniqueId targetUniqueId{}; @@ -22,7 +23,7 @@ namespace Main return targetAccountId == other.targetAccountId; } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/PlayerLists/SingleLobbyClanList.h b/MainServer/include/Structures/PlayerLists/SingleLobbyClanList.h index 9d5e624a..19d71194 100644 --- a/MainServer/include/Structures/PlayerLists/SingleLobbyClanList.h +++ b/MainServer/include/Structures/PlayerLists/SingleLobbyClanList.h @@ -2,12 +2,13 @@ #define SINGLE_LOBBY_CLAN_LIST_H #include "../../../include/Structures/AccountInfo/MainAccountUniqueId.h" +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct SingleLobbyClanList { char name[16]{}; @@ -15,7 +16,7 @@ namespace Main std::uint16_t level : 7; std::uint16_t unkown = 0; }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/PlayerLists/SingleLobbyList.h b/MainServer/include/Structures/PlayerLists/SingleLobbyList.h index 6a263947..174081a0 100644 --- a/MainServer/include/Structures/PlayerLists/SingleLobbyList.h +++ b/MainServer/include/Structures/PlayerLists/SingleLobbyList.h @@ -2,12 +2,13 @@ #define SINGLE_LOBBY_LIST_H #include "../../../include/Structures/AccountInfo/MainAccountUniqueId.h" +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct SinglePlayerInfoList { char name[16]{}; @@ -16,7 +17,7 @@ namespace Main std::uint64_t clanLogoBackId : 14; std::uint64_t level : 7; }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/ReportInfo.h b/MainServer/include/Structures/ReportInfo.h new file mode 100644 index 00000000..d9a8d45d --- /dev/null +++ b/MainServer/include/Structures/ReportInfo.h @@ -0,0 +1,45 @@ +#ifndef REPORT_INFO_H +#define REPORT_INFO_H + +#include +#include +#include + +namespace Main +{ + namespace Structures + { + struct ReportInfo + { + std::uint32_t reportId{}; + std::uint32_t reporterAccountId{}; + std::uint32_t reportedAccountId{}; + std::string reporterNickname{}; + std::string reportedNickname{}; + std::string reason{}; + std::uint32_t roomNumber{}; + std::time_t timestamp{}; + bool isAcknowledged{ false }; + std::string acknowledgedBy{}; + std::time_t acknowledgedTime{}; + + ReportInfo() = default; + + ReportInfo(std::uint32_t id, std::uint32_t reporterId, std::uint32_t reportedId, + const std::string& reporterName, const std::string& reportedName, + const std::string& reportReason, std::uint32_t roomNum) + : reportId(id) + , reporterAccountId(reporterId) + , reportedAccountId(reportedId) + , reporterNickname(reporterName) + , reportedNickname(reportedName) + , reason(reportReason) + , roomNumber(roomNum) + , timestamp(std::time(nullptr)) + { + } + }; + } +} + +#endif \ No newline at end of file diff --git a/MainServer/include/Structures/Room/ClientRoomCreationInfo.h b/MainServer/include/Structures/Room/ClientRoomCreationInfo.h index 6e495d8e..f4ca3884 100644 --- a/MainServer/include/Structures/Room/ClientRoomCreationInfo.h +++ b/MainServer/include/Structures/Room/ClientRoomCreationInfo.h @@ -5,15 +5,17 @@ #include #include "../../Common/include/Enums/RoomEnums.h" #include "../../Detail/Utilities.h" -// These structures are sent from client => server when a new room is created +#include "Macros.h" +#include +// These structures are sent from client => server when a new room is created // Team balance is apparently NOT sent ==> on room creation, set it up based on the chosen mode namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct RoomSettings { std::uint32_t time : 5 = 0; @@ -46,10 +48,9 @@ namespace Main RoomSettings() = default; }; +PACK_POP() -#pragma pack(pop) - -#pragma pack(push, 1) +PACK_PUSH(1) struct CompleteRoomInfo { RoomSettings roomSettings{}; @@ -67,7 +68,7 @@ namespace Main CompleteRoomInfo() = default; }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/Room/RoomJoin.h b/MainServer/include/Structures/Room/RoomJoin.h index 34c61f98..289a9bd7 100644 --- a/MainServer/include/Structures/Room/RoomJoin.h +++ b/MainServer/include/Structures/Room/RoomJoin.h @@ -3,6 +3,9 @@ #include #include "Utils/Constants.h" +#include "Macros.h" +#include + // This structure is sent from server => client when a room is entered // // MISSING: specific room setting [eli => num of rounds, TDM => num of total kills, etc.] @@ -10,7 +13,7 @@ namespace Main { namespace Structures { -#pragma pack(push,1) +PACK_PUSH(1) struct RoomJoin { std::uint32_t map : 7 = 0; @@ -48,9 +51,9 @@ namespace Main std::memcpy(this->password, password.data(), Common::Constants::maxPassword); } }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct RoomInviteFollow { std::uint16_t serverId{}; @@ -61,7 +64,7 @@ namespace Main char roomTitle[32]{}; //char password[14]{}; }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/Room/RoomJoinLatestInfo.h b/MainServer/include/Structures/Room/RoomJoinLatestInfo.h index 2172fc0a..438a3f62 100644 --- a/MainServer/include/Structures/Room/RoomJoinLatestInfo.h +++ b/MainServer/include/Structures/Room/RoomJoinLatestInfo.h @@ -2,6 +2,7 @@ #define ROOM_JOIN_LATEST_INFO_H #include +#include "Macros.h" // This struct is sent to the client as the last one when one joins a room // It includes information (such as some room settings) that weren't sent before, e.g. "specialSetting" and "isItemOn" @@ -9,7 +10,7 @@ namespace Main { namespace Structures { -#pragma pack(push,1) +PACK_PUSH(1) struct ModeInfoTDM { std::uint64_t redscore : 8 = 0; @@ -20,9 +21,9 @@ namespace Main std::uint64_t timelimited : 5 = 0; std::uint64_t weaponlimited : 4 = 0; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push,1) +PACK_PUSH(1) struct ModeInfoFFA { std::uint64_t timelimited : 5 = 0; @@ -31,9 +32,9 @@ namespace Main std::uint64_t state : 2 = 3; std::uint64_t weaponlimited : 4 = 0; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push,1) +PACK_PUSH(1) struct ModeInfoScrimmage { std::uint64_t unknown : 45 = 0; @@ -41,8 +42,8 @@ namespace Main std::uint64_t timelimited : 5 = 0; std::uint64_t weaponlimited : 4 = 0; }; -#pragma pack(pop) - } +PACK_POP() + } } #endif \ No newline at end of file diff --git a/MainServer/include/Structures/Room/RoomPlayerClan.h b/MainServer/include/Structures/Room/RoomPlayerClan.h index fb42f8ed..f5136183 100644 --- a/MainServer/include/Structures/Room/RoomPlayerClan.h +++ b/MainServer/include/Structures/Room/RoomPlayerClan.h @@ -2,12 +2,13 @@ #define ROOM_PLAYER_CLAN_H #include +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct PlayerClan { char clanName[16]{}; @@ -17,7 +18,7 @@ namespace Main std::uint64_t unknown2 : 27 = 0; // maybe clan id? std::uint64_t unused : 3 = 0; }; -#pragma pack(pop) +PACK_POP() } } #endif \ No newline at end of file diff --git a/MainServer/include/Structures/Room/RoomPlayerInfo.h b/MainServer/include/Structures/Room/RoomPlayerInfo.h index 0779bcce..3aff31f2 100644 --- a/MainServer/include/Structures/Room/RoomPlayerInfo.h +++ b/MainServer/include/Structures/Room/RoomPlayerInfo.h @@ -5,6 +5,8 @@ #include "../AccountInfo/MainAccountUniqueId.h" #include "Utils/Constants.h" #include +#include "Macros.h" +#include // This structure represents a single player in a given room @@ -12,8 +14,7 @@ namespace Main { namespace Structures { -#pragma pack(push, 1) - +PACK_PUSH(1) // Used to retrieve the infos or the players that are already in the room, then sent to the player that joined that room struct RoomPlayerInfo { @@ -40,9 +41,9 @@ namespace Main std::memcpy(this->playerName, playerName, Common::Constants::maxNicknameSize); } }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) // Used to send the info of the player who joined to all the players that are already in the room struct RoomLatestEnteredPlayerInfo { @@ -65,7 +66,7 @@ namespace Main std::uint32_t unk : 16 = 0; std::uint32_t unk2 : 16 = 0; }; -#pragma pack(pop) +PACK_POP() } } #endif \ No newline at end of file diff --git a/MainServer/include/Structures/Room/RoomPlayerItems.h b/MainServer/include/Structures/Room/RoomPlayerItems.h index c551f079..5254462a 100644 --- a/MainServer/include/Structures/Room/RoomPlayerItems.h +++ b/MainServer/include/Structures/Room/RoomPlayerItems.h @@ -4,6 +4,7 @@ #include #include #include "../AccountInfo/MainAccountUniqueId.h" +#include "Macros.h" // This structure represents the (equipped) items of a single player in a given room @@ -11,7 +12,7 @@ namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct RoomPlayerItems { std::array equippedItems{}; @@ -20,7 +21,7 @@ namespace Main std::array equippedWeapons_2{};// unsure about this Main::Structures::UniqueId uniqueId{}; }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/Room/RoomSettingsUpdate.h b/MainServer/include/Structures/Room/RoomSettingsUpdate.h index 9f20b0ef..391653e4 100644 --- a/MainServer/include/Structures/Room/RoomSettingsUpdate.h +++ b/MainServer/include/Structures/Room/RoomSettingsUpdate.h @@ -2,12 +2,14 @@ #define ROOM_SETTINGS_UPDATE_H #include +#include "Macros.h" +#include namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct RoomSettingsUpdateBase { std::uint32_t maxPlayers : 5 = 0; // ok @@ -23,27 +25,27 @@ namespace Main std::uint32_t unknown3 : 1 = 0; std::uint32_t unknown4 : 1 = 0; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct RoomSettingsUpdateTitle { RoomSettingsUpdateBase roomSettingsUpdateBase; char title[30]{}; char padding[2]{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct RoomSettingsUpdatePassword { RoomSettingsUpdateBase roomSettingsUpdateBase; char password[9]{}; char padding[7]{}; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct RoomSettingsUpdateTitlePassword { RoomSettingsUpdateBase roomSettingsUpdateBase; @@ -68,7 +70,7 @@ namespace Main roomSettingsUpdateBase.weaponRestriction = settings.weaponRestriction; } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/Room/RoomsList.h b/MainServer/include/Structures/Room/RoomsList.h index bf031b61..7a81a846 100644 --- a/MainServer/include/Structures/Room/RoomsList.h +++ b/MainServer/include/Structures/Room/RoomsList.h @@ -4,12 +4,14 @@ #include #include #include "Utils/Constants.h" +#include "Macros.h" +#include namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct SingleRoom { char title[30]{}; @@ -40,16 +42,16 @@ namespace Main SingleRoom() = default; }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct RoomsList { std::uint16_t totalRooms1{}; std::uint16_t totalRooms2{}; // apparently must be the same as totalRooms1x std::vector rooms{}; }; -#pragma pack(pop) +PACK_POP() } } #endif \ No newline at end of file diff --git a/MainServer/include/Structures/TradeSystem/TradeAck.h b/MainServer/include/Structures/TradeSystem/TradeAck.h index f9a0856c..f8ba18c1 100644 --- a/MainServer/include/Structures/TradeSystem/TradeAck.h +++ b/MainServer/include/Structures/TradeSystem/TradeAck.h @@ -3,18 +3,19 @@ #include #include "../AccountInfo/MainAccountUniqueId.h" +#include "Macros.h" namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct TradeAck { UniqueId uniqueId{}; std::uint32_t accountId{}; }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/TradeSystem/TradePlayerInfo.h b/MainServer/include/Structures/TradeSystem/TradePlayerInfo.h index 4ecda2f4..9c8b7f3b 100644 --- a/MainServer/include/Structures/TradeSystem/TradePlayerInfo.h +++ b/MainServer/include/Structures/TradeSystem/TradePlayerInfo.h @@ -2,13 +2,14 @@ #define TRADE_PLAYER_INFO_H #include +#include "Macros.h" // Whenever one player adds an item to the trade system this structure is used namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct TradePlayerInfo { std::uint32_t unused{}; @@ -22,7 +23,7 @@ namespace Main { } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/include/Structures/TradeSystem/TradeSystemItem.h b/MainServer/include/Structures/TradeSystem/TradeSystemItem.h index 47ab20be..ec89d5e7 100644 --- a/MainServer/include/Structures/TradeSystem/TradeSystemItem.h +++ b/MainServer/include/Structures/TradeSystem/TradeSystemItem.h @@ -3,13 +3,14 @@ #include #include "../Item/MainItemSerialInfo.h" +#include "Macros.h" // Whenever one player adds an item to the trade system this structure is used namespace Main { namespace Structures { -#pragma pack(push, 1) +PACK_PUSH(1) struct TradeAddedItemDetailed { std::uint32_t unused{}; @@ -25,9 +26,9 @@ namespace Main { } }; -#pragma pack(pop) +PACK_POP() -#pragma pack(push, 1) +PACK_PUSH(1) struct TradeBasicItem { Main::Structures::ItemId itemId; @@ -40,7 +41,7 @@ namespace Main this->itemSerialInfo = itemSerialInfo; } }; -#pragma pack(pop) +PACK_POP() } } diff --git a/MainServer/src/ChatCommands/ChatCommands.cpp b/MainServer/src/ChatCommands/ChatCommands.cpp index f21d9e60..72bcc658 100644 --- a/MainServer/src/ChatCommands/ChatCommands.cpp +++ b/MainServer/src/ChatCommands/ChatCommands.cpp @@ -1,12 +1,8 @@ +#include "../../include/ChatCommands/ICommand.h" #include "../../include/ChatCommands/ChatCommands.h" #include #include -#include "../../include/Network/MainSession.h" -#include "../../include/Network/MainSessionManager.h" -#include "Network/Packet.h" -#include "../../include/Classes/RoomsManager.h" -#include "../../include/ChatCommands/ICommand.h" #include "../../include/MainServer.h" namespace Main diff --git a/MainServer/src/Classes/Player.cpp b/MainServer/src/Classes/Player.cpp index 448f0e81..abc45aad 100644 --- a/MainServer/src/Classes/Player.cpp +++ b/MainServer/src/Classes/Player.cpp @@ -9,6 +9,7 @@ #include "Utils/Utils.h" #include "Utils/Constants.h" #include +#include namespace Main { @@ -53,6 +54,7 @@ namespace Main { m_accountInfo.battery += m_batteryObtainedInMatch; } + m_batteryObtainedInMatch = 0; } const AccountInfo& Player::getAccountInfo() const @@ -142,7 +144,8 @@ namespace Main void Player::setPlayerName(const char* playerName) { - strcpy_s(m_accountInfo.nickname, playerName); + strncpy(m_accountInfo.nickname, playerName, sizeof(m_accountInfo.nickname) - 1); + m_accountInfo.nickname[sizeof(m_accountInfo.nickname) - 1] = '\0'; } bool Player::hasEnoughInventorySpace(std::uint16_t totalNewItems) const @@ -241,6 +244,22 @@ namespace Main return m_isRoomCreationEnabled; } + void Player::disableVotekick() + { + m_isVotekickEnabled = false; + } + + void Player::enableVotekick() + { + m_isVotekickEnabled = true; + } + + bool Player::isVotekickEnabled() const noexcept + { + return m_isVotekickEnabled; + } + + void Player::unmute() { m_isMuted = false; @@ -315,6 +334,20 @@ namespace Main m_friends[ffriend] = std::weak_ptr{}; } + std::optional Player::getBossBattleTicket() const + { + for (const auto& [itemNumber, item] : m_itemsByItemNumber) + { + const auto id = item.itemId.itemId; + if (id >= 4811300 && id <= 4811600) + { + return item.serialInfo; + } + } + return std::nullopt; + } + + std::optional Player::addOnlineFriend(std::shared_ptr session) { if (session) @@ -366,6 +399,59 @@ namespace Main return it != m_equippedItemByCharacter.end() ? std::make_optional(it->id) : std::nullopt; } + std::optional> + Player::findItemIdAndDurabilityBySerialInfo(const Main::Structures::ItemSerialInfo& itemSerialInfo) const + { + if (auto it = m_itemsByItemNumber.find(itemSerialInfo.itemNumber); + it != m_itemsByItemNumber.end()) + { + return std::make_pair(it->second.itemId.itemId, it->second.durability); + } + + auto it = std::find_if( + m_equippedItemByCharacter.begin(), + m_equippedItemByCharacter.end(), + [&itemSerialInfo](const auto& equippedItem) + { + return equippedItem.serialInfo.itemNumber == itemSerialInfo.itemNumber; + } + ); + + if (it != m_equippedItemByCharacter.end()) + { + return std::make_pair(it->id, it->durability); + } + + return std::nullopt; + } + + bool Player::isItemTradeable(const Main::Structures::ItemSerialInfo& itemSerialInfo) const + { + if (auto it = m_itemsByItemNumber.find(itemSerialInfo.itemNumber); + it != m_itemsByItemNumber.end()) + { + const auto& item = it->second; + return Main::CdbUtils::isTradeable(item.itemId.itemId).value_or(false) + && item.itemId.itemId != 1000000 + && item.expirationDate == 0; + } + + auto it = std::find_if(m_equippedItemByCharacter.begin(), m_equippedItemByCharacter.end(), + [&itemSerialInfo](const auto& equippedItem) + { + return equippedItem.serialInfo.itemNumber == itemSerialInfo.itemNumber; + }); + + if (it != m_equippedItemByCharacter.end()) + { + return Main::CdbUtils::isTradeable(it->id).value_or(false) + && it->id != 1000000 + && it->expirationDate == 0; + } + + return false; + } + std::optional Player::findMaxItemNumber() const { std::optional maxItemNumber = std::nullopt; @@ -436,6 +522,25 @@ namespace Main return equippedItems; } + std::vector Player::getUnlimitedEquippedWeaponsFor(std::uint16_t characterID) const + { + std::vector unlimitedItems; + + if (characterID >= Common::Enums::MAX_CHARACTERS) + return unlimitedItems; + + const std::size_t startIndex = characterID * Common::Enums::MAX_ITEMTYPE; + const std::size_t endIndex = startIndex + Common::Enums::MAX_ITEMTYPE; + + for (std::size_t i = startIndex; i < endIndex && i < m_equippedItemByCharacter.size(); ++i) + { + const auto& item = m_equippedItemByCharacter[i]; + if (item.id != 0 && item.expirationDate == 0 && Common::Enums::isWeapon(static_cast(item.type))) + unlimitedItems.push_back(item); + } + + return unlimitedItems; + } const std::array& Player::getEquippedItems() const { @@ -535,27 +640,132 @@ namespace Main } } + std::vector Player::reduceEquippedItemsDurabilities( + std::size_t characterID, std::uint32_t weaponRestrictionValue) + { + using namespace Common::Enums; + + WeaponRestriction weaponRestriction = static_cast(weaponRestrictionValue); + std::vector damages; + + const std::size_t startIndex = characterID * MAX_ITEMTYPE; + const std::size_t endIndex = startIndex + MAX_ITEMTYPE; + + for (std::size_t i = startIndex; i < endIndex && i < m_equippedItemByCharacter.size(); ++i) + { + auto& item = m_equippedItemByCharacter[i]; + + if (item.id == 0 || item.expirationDate != 0 || !isWeapon(static_cast(item.type))) + continue; + + if (weaponRestriction != All && weaponRestriction != WeaponSelect) + { + ItemType restrictedType; + switch (weaponRestriction) + { + case MeleeOnly: restrictedType = MELEE; break; + case RifleOnly: restrictedType = RIFLE; break; + case ShotgunOnly: restrictedType = SHOTGUN; break; + case SniperOnly: restrictedType = SNIPER; break; + case GatlingOnly: restrictedType = MG; break; + case BazookaOnly: restrictedType = BAZOOKA; break; + case GrenadeOnly: restrictedType = GRENADE; break; + default: continue; + } + + if (item.type != static_cast(restrictedType)) + continue; + } + + const auto baseDurability = Main::CdbUtils::getItemDurability(item.id); + if (!baseDurability || *baseDurability == 0) + continue; + + const std::uint32_t reduction = (*baseDurability / 100) * 1; + const std::uint32_t newDurability = (*baseDurability > reduction) ? (*baseDurability - reduction) : 0; + + item.durability = newDurability; + damages.push_back(Main::ClientData::SingleWeaponDurabilityDamage{ item.serialInfo, reduction }); + } + + return damages; + } + + bool Player::updateItemDurabilityByNumber(std::uint32_t itemNumber, std::uint32_t newDurability) + { + auto updateDurability = [&](auto& item) { + item.durability = newDurability; + return true; + }; + if (auto it = m_itemsByItemNumber.find(itemNumber); it != m_itemsByItemNumber.end()) + { + return updateDurability(it->second); + } + + const std::size_t offset = m_accountInfo.latestSelectedCharacter * Common::Enums::MAX_ITEMTYPE; + if (offset + Common::Enums::MAX_ITEMTYPE > m_equippedItemByCharacter.size()) + { + return false; + } + + for (std::size_t i = 0; i < Common::Enums::MAX_ITEMTYPE; ++i) + { + auto& equippedItem = m_equippedItemByCharacter[offset + i]; + if (equippedItem.serialInfo.itemNumber == itemNumber) + { + return updateDurability(equippedItem); + } + } + return false; + } + std::optional> Player::addEnergyToItem(const Main::Structures::ItemSerialInfo& itemSerialInfo, std::uint32_t energyAdded) { auto updateEnergyAndBattery = [&](auto& item) { item.energy += energyAdded; m_accountInfo.battery -= energyAdded; - return std::pair{ item.energy, m_accountInfo.battery }; + return std::pair{ item.energy, static_cast(m_accountInfo.battery) }; }; if (auto it = m_itemsByItemNumber.find(itemSerialInfo.itemNumber); it != m_itemsByItemNumber.end()) { return updateEnergyAndBattery(it->second); } + const std::size_t offset = m_accountInfo.latestSelectedCharacter * Common::Enums::MAX_ITEMTYPE; for (std::size_t i = 0; i < Common::Enums::MAX_ITEMTYPE; ++i) { + if (offset + i >= m_equippedItemByCharacter.size()) continue; + auto& equippedItem = m_equippedItemByCharacter[offset + i]; if (equippedItem.serialInfo.itemNumber == itemSerialInfo.itemNumber) { return updateEnergyAndBattery(equippedItem); } } + + return std::nullopt; + } + + std::optional Player::getItemEnergy(const Main::Structures::ItemSerialInfo& itemSerialInfo) const + { + if (auto it = m_itemsByItemNumber.find(itemSerialInfo.itemNumber); it != m_itemsByItemNumber.end()) + { + return it->second.energy; + } + + const std::size_t offset = m_accountInfo.latestSelectedCharacter * Common::Enums::MAX_ITEMTYPE; + for (std::size_t i = 0; i < Common::Enums::MAX_ITEMTYPE; ++i) + { + if (offset + i >= m_equippedItemByCharacter.size()) continue; + + const auto& equippedItem = m_equippedItemByCharacter[offset + i]; + if (equippedItem.serialInfo.itemNumber == itemSerialInfo.itemNumber) + { + return equippedItem.energy; + } + } + return std::nullopt; } @@ -595,7 +805,12 @@ namespace Main } const std::size_t charIndex = character == -1 ? m_accountInfo.latestSelectedCharacter : character; - const auto& setItem = m_equippedItemByCharacter[charIndex * Common::Enums::MAX_ITEMTYPE + Common::Enums::ItemType::SET]; + const std::size_t setIndex = charIndex * Common::Enums::MAX_ITEMTYPE + Common::Enums::ItemType::SET; + + if (setIndex >= m_equippedItemByCharacter.size()) + return; + + const auto& setItem = m_equippedItemByCharacter[setIndex]; if (const auto entry = setItems::getInstance().getEntry(setItem.id); entry && setItem.serialInfo.itemNumber) @@ -681,7 +896,7 @@ namespace Main else { --it->second.itemId.stock; - return { Common::Enums::MATCHITEM_STOCKS_REDUCED_SUCCESS, it->second.itemId.stock }; + return { Common::Enums::MATCHITEM_STOCKS_REDUCED_SUCCESS, static_cast(it->second.itemId.stock) }; } } else @@ -699,6 +914,9 @@ namespace Main const std::size_t startIndex = characterId * Common::Enums::MAX_ITEMTYPE; for (std::size_t itemIndex = 0; itemIndex < Common::Enums::MAX_ITEMTYPE; ++itemIndex) { + if (startIndex + itemIndex >= m_equippedItemByCharacter.size()) + break; + const auto& equippedItem = m_equippedItemByCharacter[startIndex + itemIndex]; if (equippedItem.serialInfo.itemNumber == itemNumber) { @@ -712,17 +930,19 @@ namespace Main void Player::equipItemIfNotEquipped(std::uint64_t itemNumber, std::uint32_t characterId, Main::Persistence::MainScheduler& scheduler) { if (characterId >= Common::Enums::MAX_CHARACTERS) return; + const std::size_t startIndex = characterId * Common::Enums::MAX_ITEMTYPE; for (std::size_t itemIndex = 0; itemIndex < Common::Enums::MAX_ITEMTYPE; ++itemIndex) { if (startIndex + itemIndex >= m_equippedItemByCharacter.size()) continue; + const auto& equippedItem = m_equippedItemByCharacter[startIndex + itemIndex]; if (equippedItem.serialInfo.itemNumber == itemNumber) return; } + equipItem(static_cast(itemNumber), scheduler, characterId); } - std::pair, std::array> Player::getEquippedItemsSeparated() const { std::array equippedPlayerItems{}; diff --git a/MainServer/src/Classes/Room.cpp b/MainServer/src/Classes/Room.cpp index 75414f36..7d1600d1 100644 --- a/MainServer/src/Classes/Room.cpp +++ b/MainServer/src/Classes/Room.cpp @@ -22,6 +22,7 @@ #include #include +#include namespace Main { @@ -1118,22 +1119,17 @@ namespace Main bool Room::isEveryoneCsd() const { - auto& pair = m_players[0]; - auto hostSession = pair.second.lock(); - if (!hostSession) return false; - if (m_players.empty()) return false; - bool ret = true; - for (auto& [roomInfo, session] : m_players) - { - if (auto actSession = session.lock(); actSession && roomInfo.state == Common::Enums::STATE_READY && !actSession->hasCsdItems()) - { - hostSession->sendMessage("(error) Player " + std::string{ actSession->getAccountInfo().nickname } + " is not CSD"); - ret = false; - } - } - return ret; + return checkWeapons( + [](const std::shared_ptr& s) { return s->hasCsdItems(); }, "is not CSD"); } + bool Room::isEveryoneBasic() const + { + return checkWeapons( + [](const std::shared_ptr& s) { return s->hasBasicItems(); }, "doesn't have basic weapons"); + } + + // Refactored void Room::startMatch() { @@ -1151,33 +1147,31 @@ namespace Main m_hasMatchStarted = true; m_matchStartTime = Main::Details::getUtcTimeMs(); setStateFor(pair, Common::Enums::PlayerState::STATE_NORMAL); - - if (isAssassinMode() || (m_settings.map == Common::Enums::AcademyTrainingGround && m_settings.mode == Common::Enums::FreeForAll)) + + std::vector playerTeamBatch; + for (auto& pair : ranges::views::concat(m_players, m_observerPlayers)) { - std::vector playerTeamBatch; - for (auto& pair : ranges::views::concat(m_players, m_observerPlayers)) + if (pair.first.state == Common::Enums::STATE_READY || hostSession->getId() == pair.first.uniqueId.session) { - if (pair.first.state == Common::Enums::STATE_READY || hostSession->getId() == pair.first.uniqueId.session) + if (auto session = pair.second.lock()) { - if (auto session = pair.second.lock()) - { - setStateFor(pair, Common::Enums::PlayerState::STATE_NORMAL); - - Main::ClientData::PlayerTeamInfo info; - info.uid = pair.first.uniqueId; - info.team = pair.first.team; - std::memcpy(info.nickname, session->getAccountInfo().nickname, 16); - playerTeamBatch.push_back(info); - } + session->m_totalBossBattleRespawnsLeft = 3; + setStateFor(pair, Common::Enums::PlayerState::STATE_NORMAL); + + Main::ClientData::PlayerTeamInfo info; + info.uid = pair.first.uniqueId; + info.team = pair.first.team; + std::memcpy(info.nickname, session->getAccountInfo().nickname, 16); + playerTeamBatch.push_back(info); } } + } - if (!playerTeamBatch.empty()) + if (!playerTeamBatch.empty()) + { + if (!Main::Ipc::M2C_sendPlayerTeamInfoBatch(hostSession->getId(), playerTeamBatch)) { - if (!Main::Ipc::M2C_sendPlayerTeamInfoBatch(hostSession->getId(), playerTeamBatch)) - { - Utils::Logger::log("Failed to send player team batch to Cast Server", Utils::LogType::Error); - } + Utils::Logger::log("Failed to send player team batch to Cast Server", Utils::LogType::Error); } } } @@ -1294,7 +1288,7 @@ namespace Main { if (team == Common::Enums::TEAM_RED) ++m_redPoints; else ++m_bluePoints; - } + } // Refactored void Room::sendTo(const Main::Structures::UniqueId& uniqueId, const Common::Network::Packet& packet) @@ -1329,7 +1323,7 @@ namespace Main // Refactored - void Room::storeEndMatchStatsFor(const Main::Structures::UniqueId& uniqueId, const Main::Structures::ScoreboardResponse& stats, + void Room::storeEndMatchStatsFor(const Main::Structures::UniqueId& uniqueId, const Main::Structures::ScoreboardResponse& stats, std::uint32_t blueScore, std::uint32_t redScore, bool hasLeveledUp, const Main::Structures::EventMissionInfo& eventMissionInfo) { for (auto& [roomInfo, weakSession] : m_players) @@ -1339,36 +1333,56 @@ namespace Main if (roomInfo.uniqueId == uniqueId) { Main::Enums::MatchEnd matchEnd = (redScore == blueScore) ? Main::Enums::MATCH_DRAW - : ((blueScore > redScore && roomInfo.team == Common::Enums::TEAM_BLUE) || + : ((blueScore > redScore && roomInfo.team == Common::Enums::TEAM_BLUE) || (redScore > blueScore && roomInfo.team == Common::Enums::TEAM_RED)) ? Main::Enums::MATCH_WON : Main::Enums::MATCH_LOST; if (m_settings.mode == Common::Enums::ZombieMode || m_settings.mode == Common::Enums::FreeForAll - || m_settings.mode == Common::Enums::BossBattle || m_settings.mode == Common::Enums::ArmsRace - || m_settings.mode == Common::Enums::SquareMode) + || m_settings.mode == Common::Enums::BossBattle || m_settings.mode == Common::Enums::ArmsRace + || m_settings.mode == Common::Enums::SquareMode || m_settings.mode == Common::Enums::AiBattle) { matchEnd = Main::Enums::MATCH_DO_NOTHING; } - session->storeEndMatchStats((Main::Details::getUtcTimeMs() - session->getMatchStartTime()) / 1000, - stats, matchEnd, hasLeveledUp, m_settings.mode == Common::Enums::ZombieMode, + + const auto matchStartTime = session->getMatchStartTime(); + const auto currentTime = Main::Details::getUtcTimeMs(); + std::uint32_t matchDurationSeconds = 0; + if (matchStartTime != 0) + { + const auto durationMs = currentTime - matchStartTime; + if (durationMs > 0) + { + matchDurationSeconds = static_cast(durationMs / 1000); + if (matchDurationSeconds > 7200) matchDurationSeconds = 0; + } + } + + session->storeEndMatchStats(matchDurationSeconds, stats, matchEnd, hasLeveledUp, m_settings.mode == Common::Enums::ZombieMode, session->getPlayer().getRoomNumber() >= Common::Constants::clanRoomNumberStart); const std::uint32_t now = static_cast(std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); if (now >= eventMissionInfo.startDate && now <= eventMissionInfo.endDate) { - for (std::uint32_t weaponIndex = 0; const auto & kills : stats.weaponKills()) + if (m_settings.mode == Common::Enums::ZombieMode && (stats.totalKills / 3) >= 1) // >= 1 zombie kills per match = 1 pt { - if (kills >= 10) - { - auto it = weaponToMissionIndex.find(weaponIndex); - if (it != weaponToMissionIndex.end()) - { - ClientData::EventMissionPoint eventMission{ it->second }; - session->sendEventMission(eventMission); - } - } - ++weaponIndex; + session->sendEventMission(ClientData::EventMissionPoint{ 1 }); + } + if (m_settings.mode == Common::Enums::ZombieMode && stats.meleeKills >= 2) // >= 2 infections per match = 1 pt + { + session->sendEventMission(ClientData::EventMissionPoint{ 2 }); + } + if (stats.headshots >= 2) // >= 2 headshots per match = 1 pt + { + session->sendEventMission(ClientData::EventMissionPoint{ 3 }); + } + if (stats.totalKills >= 15) // >= total kills >= 15 per match = 1 pt + { + session->sendEventMission(ClientData::EventMissionPoint{ 4 }); + } + if (stats.mgKills >= 5) // >= mgKills >= 5 per match = 1 pt + { + session->sendEventMission(ClientData::EventMissionPoint{ 5 }); } } return; diff --git a/MainServer/src/Detail/Utilities.cpp b/MainServer/src/Detail/Utilities.cpp index 3c720b93..b74d89a2 100644 --- a/MainServer/src/Detail/Utilities.cpp +++ b/MainServer/src/Detail/Utilities.cpp @@ -3,6 +3,7 @@ #include "../../include/Classes/Room.h" #include "../../include/Classes/RoomsManager.h" + namespace Main { namespace Details diff --git a/MainServer/src/GeneratedCommands.cpp b/MainServer/src/GeneratedCommands.cpp index f48336ce..a1c293be 100644 --- a/MainServer/src/GeneratedCommands.cpp +++ b/MainServer/src/GeneratedCommands.cpp @@ -5,11 +5,13 @@ #include "../include/ChatCommands/Commands/ChangeHost.h" #include "../include/ChatCommands/Commands/ChangeRoomTitle.h" #include "../include/ChatCommands/Commands/Close.h" +#include "../include/ChatCommands/Commands/ConvertRtToCoupons.h" #include "../include/ChatCommands/Commands/CsdMode.h" #include "../include/ChatCommands/Commands/DebugRoom.h" #include "../include/ChatCommands/Commands/Disconnect.h" #include "../include/ChatCommands/Commands/EnableRoomCreation.h" #include "../include/ChatCommands/Commands/EnableRoomCreationFor.h" +#include "../include/ChatCommands/Commands/EnableVotekickFor.h" #include "../include/ChatCommands/Commands/GetItem.h" #include "../include/ChatCommands/Commands/Invisible.h" #include "../include/ChatCommands/Commands/JoinInvisible.h" @@ -20,6 +22,7 @@ #include "../include/ChatCommands/Commands/OnlineCommand.h" #include "../include/ChatCommands/Commands/RandomCommands.h" #include "../include/ChatCommands/Commands/SendGift.h" +#include "../include/ChatCommands/Commands/SendRewards.h" #include "../include/ChatCommands/Commands/SetCurrency.h" #include "../include/ChatCommands/Commands/SetEventCommands.h" #include "../include/ChatCommands/Commands/SetLevel.h" @@ -27,3 +30,7 @@ #include "../include/ChatCommands/Commands/Shutdown.h" #include "../include/ChatCommands/Commands/TestCommand.h" #include "../include/ChatCommands/Commands/Unban.h" +#include "../include/ChatCommands/Commands/StaffCommand.h" +#include "../include/ChatCommands/Commands/Report.h" +#include "../include/ChatCommands/Commands/ReportReceived.h" +#include "../include/ChatCommands/Commands/Reports.h" diff --git a/MainServer/src/MainServer.cpp b/MainServer/src/MainServer.cpp index d84f08fe..086ca862 100644 --- a/MainServer/src/MainServer.cpp +++ b/MainServer/src/MainServer.cpp @@ -16,8 +16,9 @@ #include "../include/Handlers/Item/DeleteItemHandler.h" #include "../include/Handlers/Item/GeneralItemHandler.h" #include "../include/Handlers/Player/Mailbox/MailboxHandlers.h" -#include "../include/Handlers/Player/Mailbox/Gifthandlers.h" +#include "../include/Handlers/Player/Mailbox/GiftHandlers.h" #include "../include/Handlers/Item/ItemUpgradeHandler.h" +#include "../include/Handlers/Item/ItemRepairHandler.h" #include "../include/Handlers/CapsuleSpinHandler.h" #include "../include/Handlers/Room/RoomCreationHandler.h" #include "../include/Handlers/Room/RoomsListHandler.h" @@ -48,13 +49,14 @@ #include "../include/Handlers/Trade/TradeLockHandler.h" #include "../include/Handlers/Trade/TradeFinalizeHandler.h" #include "../include/Handlers/Trade/TradeCancelHandler.h" +#include "../include/Handlers/Item/GambleItemHandler.h" + // IPC Auth<=>Main #include "../include/Handlers/IPC/AuthMainCallbacks.h" #include "../include/Handlers/IPC/CastMainCallbacks.h" #include -#include #include "boost/beast.hpp" #include "../include/Network/HttpSession.h" @@ -74,7 +76,7 @@ namespace Main m_chatCommands.m_scheduler = &m_scheduler; // Events - m_capsuleListDb = m_scheduler.immediatePersist(std::source_location::current(), + m_capsuleSaleEvent = m_scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getCapsuleEvent).value_or(Main::Structures::CapsuleListDatabase{}); m_eventMissionInfo = m_scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::getEventMissionsInfo).value_or(Main::Structures::EventMissionInfo{}); @@ -165,9 +167,11 @@ namespace Main std::shared_ptr session) { Main::Handlers::handleMailboxGiftSend(request, session, m_serverId); }); CN::Session::addCallback(67, [&](const Common::Network::Packet& request, std::shared_ptr session) { session->displayGiftboxes(static_cast(request.getMission())); }); - CN::Session::addCallback(68, [&](const Common::Network::Packet& request, - std::shared_ptr session) { Main::Handlers::handleInitialPlayerInfos(request, session, m_sessionsManager, m_scheduler, m_timeSinceLastRestart, - m_serverId, m_isServerOffline, m_isPublic, m_eventMissionInfo, m_expMpEvent); }); + CN::Session::addCallback(68, [&](const Common::Network::Packet& request, + std::shared_ptr session) { + Main::Handlers::handleInitialPlayerInfos(request, session, m_sessionsManager, m_scheduler, m_timeSinceLastRestart, + m_serverId, m_isServerOffline, m_isPublic, m_eventMissionInfo, m_expMpEvent, m_reportManager); + }); CN::Session::addCallback(71, [&](const Common::Network::Packet& request, std::shared_ptr session) { Main::Handlers::handlePing(request, session, m_roomsManager, Details::parseData(request), m_scheduler); }); @@ -193,9 +197,10 @@ namespace Main CN::Session::addCallback(92, [&](const Common::Network::Packet& request, std::shared_ptr session) { - Main::Handlers::handleCapsuleSpin(request, session, m_sessionsManager, Details::parseData(request)); + Main::Handlers::handleCapsuleSpin(request, session, m_sessionsManager, Details::parseData(request), m_capsuleSaleEvent); }); + CN::Session::addCallback(97, Main::Handlers::handleItemRepair); CN::Session::addCallback(100, [&](const Common::Network::Packet& request, std::shared_ptr session) { session->refundItem(Details::parseData(request)); }); @@ -260,13 +265,14 @@ namespace Main CN::Session::addCallback(142, [&](const Common::Network::Packet& request, std::shared_ptr session) { Main::Handlers::handleRoomsList(request, session, m_roomsManager); }); CN::Session::addCallback(158, [&](const Common::Network::Packet& request, - std::shared_ptr session) { Main::Handlers::handlePlayerState(request, session, m_roomsManager, m_capsuleListDb); }); + std::shared_ptr session) { Main::Handlers::handlePlayerState(request, session, m_roomsManager, m_capsuleSaleEvent); }); CN::Session::addCallback(159, [&](const Common::Network::Packet& request, std::shared_ptr session) { Main::Handlers::handleRoomMiscellaneous(request, session, m_roomsManager, m_timeSinceLastRestart); }); // Team switch CN::Session::addCallback(162, [&](const Common::Network::Packet& request, std::shared_ptr session) { Main::Handlers::handleLobbyChatMessage(request, session, m_sessionsManager, m_chatCommands, m_roomsManager, m_scheduler, *this); }); CN::Session::addCallback(161, [&](const Common::Network::Packet& request, std::shared_ptr session) { Main::Handlers::handleRoomChatMessage(request, session, m_sessionsManager, m_chatCommands, m_roomsManager, m_scheduler, *this); }); + CN::Session::addCallback(200, Main::Handlers::handleGambleItem); CN::Session::addCallback(256, [&](const Common::Network::Packet& request, std::shared_ptr session) { Main::Handlers::handleMatchLeave(request, session, m_sessionsManager, m_roomsManager, m_clansManager); }); @@ -303,14 +309,7 @@ namespace Main // Boss battle - respawning CN::Session::addCallback(329, [&](const Common::Network::Packet& request, - std::shared_ptr session) { std::cout << "329 called; Extra: " << (uint32_t)request.getExtra() << '\n'; - auto response = request; - response.setExtra(1); - uint32_t data = 2; - response.setData(reinterpret_cast(&data), sizeof(data)); - session->asyncWrite(response); }); - - + std::shared_ptr session) { session->respawnBossBattle(); }); // CLANS -- TBA // CLAN TODO ====> Handle client crash or client closed! diff --git a/MainServer/src/MainServerMain.cpp b/MainServer/src/MainServerMain.cpp index 2a479bac..aaa579e1 100644 --- a/MainServer/src/MainServerMain.cpp +++ b/MainServer/src/MainServerMain.cpp @@ -56,6 +56,11 @@ void initializeCdbFiles() Common::ConstantDatabase::CdbSingleton::initialize( Common::ConstantDatabase::CdbSingleton::getInstance(), Common::ConstantDatabase::CdbSingleton::getInstance()); + + const auto rareCapsuleItems = Main::CdbUtils::getAllRareCapsuleItems(); + Common::ConstantDatabase::CdbSingleton::filterGambleItemsByRareCapsules(rareCapsuleItems); + Common::ConstantDatabase::CdbSingleton::filterGambleItemsByRareCapsules(rareCapsuleItems); + Utils::Logger::newline(); Utils::Logger::log("Constant database cache successfully initialized", Utils::LogType::Info); @@ -65,7 +70,7 @@ void initializeCdbFiles() int main() { - SetConsoleTitleW(L"Microvolts Main Server"); + Common::Utils::setConsoleTitle(L"Microvolts Main Server"); printInitialInformation(); initializeCdbFiles(); @@ -83,6 +88,23 @@ int main() Utils::Logger::log(std::format("Website Information: IP: {}, Port: {}", websiteInfo.ip, websiteInfo.port), Utils::LogType::Normal); + const std::string banner = R"( + + _____ __ __ ____ _ ______ _ _ + / ____| \ \ / / | _ \ (_) | ____| | | | | + | (___ __\ \ /\ / /__| |_) | ___ __ _ _ _ __ | |__ _ __ ___ _ _| | __ _| |_ ___ _ __ + \___ \ / _ \ \/ \/ / _ \ _ < / _ \/ _` | | '_ \ | __| | '_ ` _ \| | | | |/ _` | __/ _ \| '__| + ____) | (_) \ /\ / __/ |_) | __/ (_| | | | | | | |____| | | | | | |_| | | (_| | || (_) | | + |_____/ \___/ \/ \/ \___|____/ \___|\__, |_|_| |_| |______|_| |_| |_|\__,_|_|\__,_|\__\___/|_| + __/ | + |___/ + + GitHub: https://github.com/SoWeBegin/MicrovoltsEmulator + +)"; + + Utils::Logger::log(banner, Utils::LogType::Info); + Main::MainServer srv(io_context, io_context_boost, parsedServerInfo, websiteInfo.port); srv.asyncAccept(); @@ -95,4 +117,4 @@ int main() t1.join(); t2.join(); -} \ No newline at end of file +} diff --git a/MainServer/src/Network/MainSession.cpp b/MainServer/src/Network/MainSession.cpp index 7e103f39..e86497d9 100644 --- a/MainServer/src/Network/MainSession.cpp +++ b/MainServer/src/Network/MainSession.cpp @@ -24,6 +24,8 @@ #include #include +#include "Macros.h" +#include namespace Main { @@ -63,11 +65,12 @@ namespace Main Common::Cryptography::Crypt cryptography; cryptography.KeySetup(0); cryptography.RC5Decrypt32(reinterpret_cast(m_reader.data()), &header, sizeof(Common::Protocol::TcpHeader)); - if (header.getCrypt() && header.getSize() >= 12 && callbackNum != 71) + + if (header.getCrypt() && header.getSize() >= 12 && m_packetReplicaWhitelist.count(callbackNum) == 0) { - m_acManager.submitEvent(std::make_unique(shared_from_this(), callbackNum, m_reader)); + m_acManager.submitEvent(std::make_unique( + shared_from_this(), callbackNum, m_reader)); } - } // For many packets, the clients assumes certain extras and logic: @@ -348,6 +351,15 @@ namespace Main targetSession->asyncWrite(m_packet); } + bool Session::removeBossBattleTicket() + { + if (auto foundBossBattleSerialInfo = m_player.getBossBattleTicket()) + { + return deleteItem(foundBossBattleSerialInfo.value(), "Boss Battle ticket removed automatically after starting Boss Battle match"); + } + return false; + } + void Session::acceptFriendRequest(std::shared_ptr senderSession, const Main::Structures::Friend& target, const std::uint8_t* const data) { const auto& accountInfo = m_player.getAccountInfo(); @@ -546,39 +558,64 @@ namespace Main } // Assumptions: serialInfo is correct - void Session::upgradeWeapon(std::uint32_t itemId, const Main::Structures::ItemSerialInfo& serialInfo, std::uint32_t mpNeeded, - bool hasParent, std::uint8_t mission, std::uint8_t option, bool useEnergyRefund, bool useGlue) + bool Session::upgradeWeapon(std::uint32_t itemId, const Main::Structures::ItemSerialInfo& serialInfo, bool hasParent, + std::uint8_t mission, std::uint8_t option, bool useEnergyRefund, bool useGlue) { + const std::uint32_t toAdd = hasParent ? 1 : (mission * 10 + 1); + const std::uint32_t newItemId = itemId + toAdd; + const auto newItemUpgradeInfo = Common::ConstantDatabase::CdbSingleton::getInstance().getEntry(newItemId); + if (!newItemUpgradeInfo) + { + sendMessage("[Session::upgradeWeapon] newItemUpgradeInfo is std::nullopt - report this issue if it's an error"); + return false; + } + static constexpr std::array unused{}; const Main::Structures::AccountInfo& accountInfo = m_player.getAccountInfo(); m_packet.setTcpHeader(m_id, Common::Enums::NO_ENCRYPTION); m_packet.setCommand(101, mission, Enums::UPGRADE_SUCCESS, option); m_packet.setData(unused.data(), unused.size()); - if (accountInfo.microPoints < mpNeeded) + if (accountInfo.microPoints < newItemUpgradeInfo->ui_buy_point) { m_packet.setExtra(Enums::NOT_ENOUGH_MP_FOR_UPGRADE); asyncWrite(m_packet); - return; + return false; + } + auto itemEnergy = m_player.getItemEnergy(serialInfo); + if (!itemEnergy) + { + m_packet.setExtra(Enums::UPGRADE_FAIL); + asyncWrite(m_packet); + sendMessage("[Session::upgradeWeapon] could not retrieve the energy for this item, please report this issue"); + return false; + } + // check that itemEnergy >= totalRequiredEnergy (from weapon CgdUtils) + if (itemEnergy.value() < newItemUpgradeInfo->ui_use_exp) + { + m_packet.setExtra(Enums::UPGRADE_FAIL); + asyncWrite(m_packet); + sendMessage("[Session::upgradeWeapon] error: selected item does not have enough energy for an upgrade!"); + return false; } - setAccountMicroPoints(accountInfo.microPoints - mpNeeded); + setAccountMicroPoints(accountInfo.microPoints - newItemUpgradeInfo->ui_buy_point); if (!useGlue && m_dist(m_gen) <= Common::Constants::upgradeFailRate) { m_packet.setExtra(Enums::UPGRADE_FAIL); asyncWrite(m_packet); if (!useEnergyRefund) - { // respawn a new item with 0 energy + { // respawn a new identical item with 0 energy replaceItem(serialInfo, itemId, "Item upgrade attempt failed for itemID: " + std::to_string(itemId)); } // if energy refund used, the item remains identical } else { asyncWrite(m_packet); - const std::uint32_t toAdd = hasParent ? 1 : (mission * 10 + 1); - replaceItem(serialInfo, itemId + toAdd, "Item upgraded successfully from ItemID: " + std::to_string(itemId) + " to ItemID: " + std::to_string(itemId + toAdd)); + replaceItem(serialInfo, newItemId, "Item upgraded successfully from ItemID: " + std::to_string(itemId) + " to ItemID: " + std::to_string(itemId + toAdd)); } sendCurrency(); + return true; } void Session::resetUpgrade(const Main::ClientData::UpgradeReset& upgradeReset) @@ -876,25 +913,29 @@ namespace Main } // This function is exclusively used for /setnickname cmd - void Session::setPlayerName(const std::string& playerName) - { - if (playerName.size() >= 16) - { - sendMessage("error: the nickname cannot have more than 16 characters"); - return; - } - const bool changed = m_scheduler.immediatePersist(std::source_location::current(), - &Main::Persistence::PersistentDatabase::updatePlayerName, m_player.getAccountID(), playerName.c_str()); - if (!changed) - { - sendMessage("error: there's already a player with this nickname"); - return; - } - else - { - m_player.setPlayerName(playerName.c_str()); - sendMessage("success (relog)"); - } + bool Session::setPlayerName(const std::string& playerName) + { + if (playerName.size() >= 16) + { + sendMessage("error: the nickname cannot have more than 16 characters"); + return false; + } + + const bool changed = m_scheduler.immediatePersist( + std::source_location::current(), + &Main::Persistence::PersistentDatabase::updatePlayerName, + m_player.getAccountID(), + playerName.c_str() + ); + + if (!changed) + { + sendMessage("error: there's already a player with this nickname"); + return false; + } + + m_player.setPlayerName(playerName.c_str()); + return true; } // option and mission are probably related to the upgrade type (power, firing rate, etc) @@ -943,7 +984,7 @@ namespace Main m_player.equipItem(itemNumber, m_scheduler); } - void Session::replaceItem(const Main::Structures::ItemSerialInfo& serialInfo, std::uint32_t newItemId, const std::string& action) + bool Session::replaceItem(const Main::Structures::ItemSerialInfo& serialInfo, std::uint32_t newItemId, const std::string& action) { // Logs auto itemIdOpt = m_player.findItemIdBySerialInfo(serialInfo); @@ -951,17 +992,17 @@ namespace Main if (!CdbUtils::itemExists(newItemId)) { sendMessage("[Session::replaceItem] error: itemID not found"); - return; + return false; } if (!m_player.hasEnoughInventorySpace(1)) { sendMessage("[Session::replaceItem] not enough inventory space!"); - return; + return false; } if (!sendDeletePacket(serialInfo)) { sendMessage("[Session::replaceItem] error: failed to delete original item"); - return; + return false; } Main::Structures::SpawnedItem spawnedItem{ newItemId }; spawnedItem.serialInfo = serialInfo; @@ -979,6 +1020,8 @@ namespace Main Main::Structures::ItemLogInfo log{ serialInfo.itemNumber, *itemIdOpt, 0, action}; m_scheduler.addRepetitiveCallback(std::source_location::current(), m_player.getAccountID(), &Main::Persistence::PersistentDatabase::insertItemLog, m_player.getAccountID(), log); + + return true; } void Session::logItemInfo(std::uint64_t itemNumber, std::uint32_t itemId, std::uint32_t expiration, const std::string& action) @@ -1014,32 +1057,73 @@ namespace Main return false; } + // use this for coupon items bool Session::spawnCoupon(const std::uint32_t total) + { + return spawnCouponCommon(total, false); + } + + // use this to spawn coupons directly to account currency + bool Session::spawnCouponImmediate(const std::uint32_t total) + { + return spawnCouponCommon(total, true); + } + + bool Session::spawnCouponCommon(const std::uint32_t total, bool immediateToAccount) { const auto res = m_player.addCoupon(total); + Main::Structures::SpawnedItem spawnedItem{ 1000000 }; + spawnedItem.itemId.stock = total; + spawnedItem.serialInfo.itemNumber = m_player.getLatestItemNumber() + 1; + spawnedItem.expirationDate = 0; + if (res.action == Common::Enums::AddCouponAction::MUST_CREATE_NEW_COUPON) { - Main::Structures::SpawnedItem spawnedItem{ 1000000 }; - spawnedItem.itemId.stock = total; - spawnedItem.serialInfo.itemNumber = m_player.getLatestItemNumber() + 1; - spawnedItem.expirationDate = 0; Item convertedItem{ spawnedItem }; convertedItem.unknown = true; - if (addItem(convertedItem, true)) + + if (!addItem(convertedItem, true)) + return false; + + if (immediateToAccount) + { + m_packet.setCommand(66, 0, 51, 2); + m_packet.setData(reinterpret_cast(&spawnedItem), sizeof(spawnedItem)); + setLatestItemNumber(spawnedItem.serialInfo.itemNumber); + asyncWrite(m_packet); + + logItemInfo(spawnedItem.serialInfo.itemNumber, spawnedItem.itemId.itemId, spawnedItem.expirationDate, + "Coupon item spawned automatically in Session::spawnCouponCommon"); + } + else { m_player.addTotalCouponItems(convertedItem); - return true; } - return false; + + return true; } else if (res.action == Common::Enums::EXISTING_COUPON_STOCK_UPDATED) { - m_scheduler.addRepetitiveCallback(std::source_location::current(), m_player.getAccountID(), - &Main::Persistence::PersistentDatabase::updateItemStock, m_player.getAccountID(), - res.serialInfo.itemNumber, res.newStock); + if (immediateToAccount) + { + // trick the client in thinking it has more coupon items, since deleting coupon items doesn't work unless we use the coupon shop packets + // this way, the client will keep the updated coupon total, consistent without relogging + spawnedItem.serialInfo.itemNumber = 0; // on purpose, this is just for the client anyway + m_packet.setCommand(66, 0, 51, 2); + m_packet.setData(reinterpret_cast(&spawnedItem), sizeof(spawnedItem)); + setLatestItemNumber(spawnedItem.serialInfo.itemNumber); + asyncWrite(m_packet); + + logItemInfo(spawnedItem.serialInfo.itemNumber, spawnedItem.itemId.itemId, spawnedItem.expirationDate, + "Coupon item spawned automatically in Session::spawnCouponCommon"); + } + + m_scheduler.addRepetitiveCallback(std::source_location::current(), m_player.getAccountID(), &Main::Persistence::PersistentDatabase::updateItemStock, + m_player.getAccountID(), res.serialInfo.itemNumber, res.newStock); return true; } + return false; } @@ -1196,6 +1280,28 @@ namespace Main setAccountRockTotens(accountInfo.rockTotens + rtToAdd); } + void Session::reduceEquippedItemsDurability(std::uint32_t weaponRestriction) + { + const auto characterID = m_player.getAccountInfo().latestSelectedCharacter; + auto weaponDurabilityDamages = m_player.reduceEquippedItemsDurabilities(characterID, weaponRestriction); + + m_scheduler.addRepetitiveCallback(std::source_location::current(), m_player.getAccountID(), &Main::Persistence::PersistentDatabase::reduceDurability, + m_player.getAccountID(), m_player.getUnlimitedEquippedWeaponsFor(characterID)); + + m_packet.setOrder(93); + m_packet.setOption(weaponDurabilityDamages.size()); + m_packet.setData(reinterpret_cast(weaponDurabilityDamages.data()), + weaponDurabilityDamages.size() * sizeof(Main::ClientData::SingleWeaponDurabilityDamage)); + asyncWrite(m_packet); + } + + void Session::updateItemDurability(std::uint32_t itemNumber, std::uint32_t newDurability) + { + m_scheduler.addRepetitiveCallback(std::source_location::current(), + m_player.getAccountID(), &Main::Persistence::PersistentDatabase::updateItemDurability, m_player.getAccountID(), itemNumber, newDurability); + m_player.updateItemDurabilityByNumber(itemNumber, newDurability); + } + void Session::sendCurrency() { // NB: Set the currency before using this function @@ -1273,7 +1379,7 @@ namespace Main using namespace std::chrono; using namespace std::literals; - zoned_time zt{ current_zone(), local_seconds{duration_cast(system_clock::now().time_since_epoch()) + seconds(daysDuration * 24 * 60 * 60)} }; + zoned_time zt{ "UTC", local_seconds{duration_cast(system_clock::now().time_since_epoch()) + seconds(daysDuration * 24 * 60 * 60)}}; const std::string bannedUntil = std::format("{:%Y-%m-%d %H:%M:%S}", zt.get_sys_time()); if (m_scheduler.immediatePersist(std::source_location::current(), @@ -1295,7 +1401,7 @@ namespace Main { using namespace std::chrono; using namespace std::literals; - zoned_time zt{ current_zone(), local_seconds{duration_cast(system_clock::now().time_since_epoch()) + seconds(daysDuration * 24 * 60 * 60)} }; + zoned_time zt{ "UTC", local_seconds{duration_cast(system_clock::now().time_since_epoch()) + seconds(daysDuration * 24 * 60 * 60)}}; const std::string mutedUntil = std::format("{:%Y-%m-%d %H:%M:%S}", zt.get_sys_time()); if (m_scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::updateMute, m_player.getAccountInfo().nickname, @@ -1312,11 +1418,16 @@ namespace Main m_player.disableRoomCreation(); } + void Session::setVotekickDisabled() + { + m_player.disableVotekick(); + } + bool Session::disableRoomCreation(std::uint64_t daysDuration) { using namespace std::chrono; using namespace std::literals; - zoned_time zt{ current_zone(), local_seconds{duration_cast(system_clock::now().time_since_epoch()) + seconds(daysDuration * 24 * 60 * 60)} }; + zoned_time zt{ "UTC", local_seconds{duration_cast(system_clock::now().time_since_epoch()) + seconds(daysDuration * 24 * 60 * 60)}}; const std::string disabledUntil = std::format("{:%Y-%m-%d %H:%M:%S}", zt.get_sys_time()); if (m_scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::updateRoomCreationDisabledUntil, m_player.getAccountInfo().nickname, @@ -1328,6 +1439,27 @@ namespace Main return false; } + bool Session::disableVotekick(std::uint64_t daysDuration) + { + using namespace std::chrono; + using namespace std::literals; + zoned_time zt{ "UTC", + local_seconds{duration_cast(system_clock::now().time_since_epoch()) + seconds(daysDuration * 24 * 60 * 60)} + }; + const std::string disabledUntil = std::format("{:%Y-%m-%d %H:%M:%S}", zt.get_sys_time()); + + if (m_scheduler.immediatePersist(std::source_location::current(), + &Main::Persistence::PersistentDatabase::updateVotekickDisabledUntil, + m_player.getAccountInfo().nickname, + disabledUntil)) + { + m_player.disableVotekick(); + return true; + } + return false; + } + + void Session::sendLobbyList(const std::vector>& allSessions) { m_packet.setTcpHeader(m_id, Common::Enums::NO_ENCRYPTION); @@ -1348,7 +1480,8 @@ namespace Main singlePlayerList.clanLogoBackId = partialAccountData.clanLogoBackId; singlePlayerList.clanLogoFrontId = partialAccountData.clanLogoFrontId; singlePlayerList.level = partialAccountData.playerLevel; - strcpy_s(singlePlayerList.name, partialAccountData.nickname); + strncpy(singlePlayerList.name, partialAccountData.nickname, sizeof(singlePlayerList.name) - 1); + singlePlayerList.name[sizeof(singlePlayerList.name) - 1] = '\0'; singlePlayerList.uniqueId.server = partialAccountData.uniqueId.server; singlePlayerList.uniqueId.session = partialAccountData.uniqueId.session; singlePlayerList.uniqueId.unknown = partialAccountData.uniqueId.unknown; @@ -1387,7 +1520,8 @@ namespace Main if (partialAccountData.clanId != selfAccountInfo.clanId) continue; // Skip non clan members Main::Structures::SingleLobbyClanList singlePlayerList; singlePlayerList.level = partialAccountData.playerLevel; - strcpy_s(singlePlayerList.name, partialAccountData.nickname); + strncpy(singlePlayerList.name, partialAccountData.nickname, sizeof(singlePlayerList.name) - 1); + singlePlayerList.name[sizeof(singlePlayerList.name) - 1] = '\0'; singlePlayerList.uniqueId.server = partialAccountData.uniqueId.server; singlePlayerList.uniqueId.session = partialAccountData.uniqueId.session; singlePlayerList.uniqueId.unknown = partialAccountData.uniqueId.unknown; @@ -1413,6 +1547,13 @@ namespace Main &Main::Persistence::PersistentDatabase::resetRoomCreationDisabledUntil, m_player.getAccountInfo().nickname); } + bool Session::enableVotekick() + { + m_player.enableVotekick(); + return m_scheduler.immediatePersist(std::source_location::current(), &Main::Persistence::PersistentDatabase::resetVotekickDisabledUntil, + m_player.getAccountInfo().nickname); + } + void Session::setMute(Main::Structures::MuteInfo val) { val.isMuted ? m_player.mute(val.reason, val.mutedBy, val.mutedUntil) : m_player.unmute(); @@ -1605,7 +1746,7 @@ namespace Main if (rewards.day < rewards.items.size() && Main::CdbUtils::itemExists(rewards.items[rewards.day])) { - m_packet.setCommand(66, 0, 51, 6); // n.b. option0, mission3 => story reward + m_packet.setCommand(66, 0, 51, 1); // n.b. option0, mission3 => story reward Main::Structures::SpawnedItem spawnedItem{ rewards.items[rewards.day] }; spawnedItem.serialInfo.itemNumber = m_player.getLatestItemNumber() + 1; addItem(Item{ spawnedItem }); @@ -1721,8 +1862,8 @@ namespace Main const std::uint32_t index = eventMission.eventIndex; if (m_eventMissions.find(index) == m_eventMissions.end()) { - sendMessage("[sendEventMission] error while sending event mission point (EventMissionIndex: " + std::to_string(index) + ", ActiveEventsSize: " + - std::to_string(m_eventMissions.size()) + ") - please report this issue"); + //sendMessage("[sendEventMission] error while sending event mission point (EventMissionIndex: " + std::to_string(index) + ", ActiveEventsSize: " + + //std::to_string(m_eventMissions.size()) + ") - please report this issue"); return; } @@ -1776,8 +1917,11 @@ namespace Main m_player.getAccountID(), &Main::Persistence::PersistentDatabase::updatePlayerMissionProgress, m_player.getAccountID(), eventIndex, Common::Constants::eventMissionTotal); - // Also send 10,000 RT for each event mission. - sendRt(10'000); + // Also send 5,000 RT for each event mission + 10 coupons + boss battle ticket + sendRt(5'000); + spawnCouponImmediate(5); + spawnItemCommand(4811300, "Item spawned automatically - event mission rewards"); + sendMessage("You obtained 5'000 RT, 5 coupons and a Boss Battle ticket!", Main::Enums::TIP); } else { @@ -1851,7 +1995,7 @@ namespace Main // Trade system void Session::temporarilySealAllItems() { -#pragma pack(push, 1) +PACK_PUSH(1) struct SealInfo { Main::Structures::ItemSerialInfo serialInfo1; @@ -1859,7 +2003,7 @@ namespace Main std::uint32_t unused2{}; Main::Structures::ItemSerialInfo serialInfo2; }; -#pragma pack(pop) +PACK_POP() Common::Network::Packet response; response.setTcpHeader(0, Common::Enums::NO_ENCRYPTION); @@ -1974,7 +2118,7 @@ namespace Main return m_player.getCurrentlyTradingWithAccountId(); } - void Session::spawnItem(std::uint32_t itemId, const Main::Structures::ItemSerialInfo& itemSerialInfo, const std::string& action) + bool Session::spawnItem(std::uint32_t itemId, const Main::Structures::ItemSerialInfo& itemSerialInfo, const std::string& action) { Common::Network::Packet response; response.setTcpHeader(0, Common::Enums::NO_ENCRYPTION); @@ -1985,31 +2129,40 @@ namespace Main spawnedItem.serialInfo.itemNumber = m_player.getLatestItemNumber() + 1; response.setData(reinterpret_cast(&spawnedItem), sizeof(spawnedItem)); asyncWrite(response); - addItem(spawnedItem); - Main::Structures::ItemLogInfo log{ itemSerialInfo.itemNumber, itemId, 0, action }; - m_scheduler.addRepetitiveCallback(std::source_location::current(), - m_player.getAccountID(), &Main::Persistence::PersistentDatabase::insertItemLog, m_player.getAccountID(), log); + if (addItem(spawnedItem)) + { + Main::Structures::ItemLogInfo log{ itemSerialInfo.itemNumber, itemId, 0, action }; + m_scheduler.addRepetitiveCallback(std::source_location::current(), + m_player.getAccountID(), &Main::Persistence::PersistentDatabase::insertItemLog, m_player.getAccountID(), log); + return true; + } + else + { + sendMessage("[Session::spawnItem] error while spawning item - please report this issue"); + return false; + } } bool Session::hasCsdItems() { - const auto equippedItems = m_player.getEquippedItemsFor(m_player.getAccountInfo().latestSelectedCharacter); - for (auto& equippedItem : equippedItems) - { - if (equippedItem.serialInfo.itemNumber == 0) continue; - const bool isEquippedItemCsd = Details::isCsdItem( - static_cast(equippedItem.type), - equippedItem.id - ); + return checkEquippedItems(Details::isCsdItem, "is not CSD"); + } - if (!isEquippedItemCsd) - { - sendMessage("Item Type " + std::to_string(equippedItem.type) + " is not CSD"); - return false; - } + bool Session::hasBasicItems() + { + return checkEquippedItems(Details::isBasicItem, "is not a basic item (Boss Battle requires basic weapons)"); + } + + void Session::respawnBossBattle() + { + if (m_totalBossBattleRespawnsLeft) + { + m_packet.setCommand(329, 0, 1, 0); + m_packet.setData(reinterpret_cast(&m_totalBossBattleRespawnsLeft), sizeof(m_totalBossBattleRespawnsLeft)); + asyncWrite(m_packet); + --m_totalBossBattleRespawnsLeft; } - return true; } }; } diff --git a/MainServer/src/Network/MainSessionManager.cpp b/MainServer/src/Network/MainSessionManager.cpp index 17890694..cbaec89c 100644 --- a/MainServer/src/Network/MainSessionManager.cpp +++ b/MainServer/src/Network/MainSessionManager.cpp @@ -141,7 +141,7 @@ namespace Main for (auto& currentSession : m_sessionsVector) { if (selfSessionId == currentSession->getId()) continue; - if (selfAccountInfo.clanId == currentSession->getAccountInfo().clanId) + if (selfAccountInfo.clanId >= 8 && selfAccountInfo.clanId == currentSession->getAccountInfo().clanId) { currentSession->asyncWrite(message); } diff --git a/MainServer/src/Persistence/MainDatabaseManager.cpp b/MainServer/src/Persistence/MainDatabaseManager.cpp index a1279506..d980620e 100644 --- a/MainServer/src/Persistence/MainDatabaseManager.cpp +++ b/MainServer/src/Persistence/MainDatabaseManager.cpp @@ -18,6 +18,7 @@ #include #include #include "Utils/SetupParser.h" +#include namespace Main { @@ -197,7 +198,7 @@ namespace Main } catch (const sql::SQLException& e) { - ::Utils::Logger::log("MariaDB exception: " + std::string(e.what()), Utils::LogType::Error, "PersistentDatabase::addChatMessage"); + return; } } @@ -279,95 +280,106 @@ namespace Main return missions; } - std::optional PersistentDatabase::getEventInfo(const std::string& tableName) - { - try - { - const std::string query = - "SELECT UNIX_TIMESTAMP(StartDate) AS StartTimestamp, " - "UNIX_TIMESTAMP(EndDate) AS EndTimestamp " - "FROM " + tableName + " LIMIT 1"; - - std::unique_ptr stmt(m_con->prepareStatement(query)); - std::unique_ptr res(stmt->executeQuery()); - - if (res->next()) - { - Main::Structures::EventMissionInfo info; - info.startDate = res->getUInt("StartTimestamp"); - info.endDate = res->getUInt("EndTimestamp"); - return info; - } - else - { - const std::string insertQuery = - "INSERT INTO " + tableName + " (StartDate, EndDate) " - "VALUES (FROM_UNIXTIME(0), FROM_UNIXTIME(0))"; - - std::unique_ptr insertStmt(m_con->prepareStatement(insertQuery)); - insertStmt->executeUpdate(); - - return Main::Structures::EventMissionInfo{ 0, 0 }; - } - } - catch (const sql::SQLException& e) - { - ::Utils::Logger::log("MariaDB exception: " + std::string(e.what()), Utils::LogType::Error, "PersistentDatabase::getEventInfo (" + tableName + ")"); - } - - return std::nullopt; - } + std::optional PersistentDatabase::getEventInfo(const std::string& tableName) + { + try + { + const std::string createTableQuery = + "CREATE TABLE IF NOT EXISTS " + tableName + " (" + "StartDate DATETIME NOT NULL, " + "EndDate DATETIME NOT NULL)"; + std::unique_ptr createStmt(m_con->prepareStatement(createTableQuery)); + createStmt->executeUpdate(); + + const std::string query = + "SELECT UNIX_TIMESTAMP(StartDate) AS StartTimestamp, " + "UNIX_TIMESTAMP(EndDate) AS EndTimestamp " + "FROM " + tableName + " LIMIT 1"; + std::unique_ptr stmt(m_con->prepareStatement(query)); + std::unique_ptr res(stmt->executeQuery()); + + if (res->next()) + { + Main::Structures::EventMissionInfo info; + info.startDate = res->getUInt("StartTimestamp"); + info.endDate = res->getUInt("EndTimestamp"); + return info; + } + else + { + const std::string insertQuery = + "INSERT INTO " + tableName + " (StartDate, EndDate) " + "VALUES (FROM_UNIXTIME(0), FROM_UNIXTIME(0))"; + std::unique_ptr insertStmt(m_con->prepareStatement(insertQuery)); + insertStmt->executeUpdate(); + return Main::Structures::EventMissionInfo{ 0, 0 }; + } + } + catch (const sql::SQLException& e) + { + ::Utils::Logger::log("MariaDB exception: " + std::string(e.what()), Utils::LogType::Error, "PersistentDatabase::getEventInfo (" + tableName + ")"); + } + + return std::nullopt; + } std::optional PersistentDatabase::getCapsuleEvent() { try - { - const std::string query = R"( - SELECT UNIX_TIMESTAMP(StartDate) AS StartTimestamp, - UNIX_TIMESTAMP(EndDate) AS EndTimestamp, - NewMpPrice, - NewRtPrice - FROM CapsuleEvents - LIMIT 1 - )"; - - std::unique_ptr stmt(m_con->prepareStatement(query)); - std::unique_ptr res(stmt->executeQuery()); - - if (res->next()) - { - Main::Structures::CapsuleListDatabase capsule; - capsule.saleEventStartDate = res->getUInt("StartTimestamp"); - capsule.saleEventEndDate = res->getUInt("EndTimestamp"); - capsule.newMpPrice = res->getUInt("NewMpPrice"); - capsule.newRtPrice = res->getUInt("NewRtPrice"); - return capsule; - } - else - { - const std::string insertQuery = R"( - INSERT INTO CapsuleEvents (StartDate, EndDate, NewMpPrice, NewRtPrice) - VALUES (FROM_UNIXTIME(0), FROM_UNIXTIME(0), 0, 0) - )"; - - std::unique_ptr insertStmt(m_con->prepareStatement(insertQuery)); - insertStmt->executeUpdate(); - - Main::Structures::CapsuleListDatabase capsule; - capsule.saleEventStartDate = 0; - capsule.saleEventEndDate = 0; - capsule.newMpPrice = 0; - capsule.newRtPrice = 0; - return capsule; - } - } - catch (const sql::SQLException& e) - { - ::Utils::Logger::log("MariaDB exception: " + std::string(e.what()), Utils::LogType::Error, "PersistentDatabase::getCapsuleEvent"); - } - - return std::nullopt; - } + { + const std::string createTableQuery = R"( + CREATE TABLE IF NOT EXISTS CapsuleEvents ( + StartDate DATETIME NOT NULL, + EndDate DATETIME NOT NULL, + NewMpPrice INT NOT NULL, + NewRtPrice INT NOT NULL + ) + )"; + std::unique_ptr createStmt(m_con->prepareStatement(createTableQuery)); + createStmt->executeUpdate(); + + const std::string query = R"( + SELECT UNIX_TIMESTAMP(StartDate) AS StartTimestamp, + UNIX_TIMESTAMP(EndDate) AS EndTimestamp, + NewMpPrice, + NewRtPrice + FROM CapsuleEvents + LIMIT 1 + )"; + + std::unique_ptr stmt(m_con->prepareStatement(query)); + std::unique_ptr res(stmt->executeQuery()); + + if (res->next()) + { + Main::Structures::CapsuleListDatabase capsule; + capsule.saleEventStartDate = res->getUInt("StartTimestamp"); + capsule.saleEventEndDate = res->getUInt("EndTimestamp"); + capsule.newMpPrice = res->getUInt("NewMpPrice"); + capsule.newRtPrice = res->getUInt("NewRtPrice"); + return capsule; + } + else + { + const std::string insertQuery = R"( + INSERT INTO CapsuleEvents (StartDate, EndDate, NewMpPrice, NewRtPrice) + VALUES (FROM_UNIXTIME(0), FROM_UNIXTIME(0), 0, 0) + )"; + + std::unique_ptr insertStmt(m_con->prepareStatement(insertQuery)); + insertStmt->executeUpdate(); + + Main::Structures::CapsuleListDatabase capsule{}; + return capsule; + } + } + catch (const sql::SQLException& e) + { + ::Utils::Logger::log("MariaDB exception: " + std::string(e.what()), Utils::LogType::Error, "PersistentDatabase::getCapsuleEvent"); + } + + return std::nullopt; + } bool PersistentDatabase::updateCapsuleEvent(const Main::Structures::CapsuleListDatabase& capsule) { @@ -567,52 +579,57 @@ namespace Main return false; } - std::optional PersistentDatabase::getExpMpBonusInfo() - { - try - { - const std::string query = R"( - SELECT UNIX_TIMESTAMP(StartDate) AS StartTimestamp, - UNIX_TIMESTAMP(EndDate) AS EndTimestamp, - ExpBonusPercent, - MpBonusPercent - FROM ExpMpBonusEvents - LIMIT 1 - )"; - - std::unique_ptr stmt(m_con->prepareStatement(query)); - std::unique_ptr res(stmt->executeQuery()); - - if (res->next()) - { - Main::Structures::ExpMpBonusInfo info; - info.startDate = res->getUInt("StartTimestamp"); - info.endDate = res->getUInt("EndTimestamp"); - info.expBonusPercent = res->getUInt("ExpBonusPercent"); - info.mpBonusPercent = res->getUInt("MpBonusPercent"); - return info; - } - else - { - const std::string insertQuery = R"( - INSERT INTO ExpMpBonusEvents (StartDate, EndDate, ExpBonusPercent, MpBonusPercent) - VALUES (FROM_UNIXTIME(0), FROM_UNIXTIME(0), 0, 0) - )"; - - std::unique_ptr insertStmt(m_con->prepareStatement(insertQuery)); - insertStmt->executeUpdate(); - - return Main::Structures::ExpMpBonusInfo{ 0, 0, 0, 0 }; - } - } - catch (const sql::SQLException& e) - { - ::Utils::Logger::log("MariaDB exception: " + std::string(e.what()), Utils::LogType::Error, "PersistentDatabase::getExpMpBonusInfo"); - } - - return std::nullopt; - } + { + try + { + const std::string createTableQuery = + "CREATE TABLE IF NOT EXISTS ExpMpBonusEvents (" + "StartDate DATETIME NOT NULL, " + "EndDate DATETIME NOT NULL, " + "ExpBonusPercent INT NOT NULL, " + "MpBonusPercent INT NOT NULL)"; + std::unique_ptr createStmt(m_con->prepareStatement(createTableQuery)); + createStmt->executeUpdate(); + + const std::string query = R"( + SELECT UNIX_TIMESTAMP(StartDate) AS StartTimestamp, + UNIX_TIMESTAMP(EndDate) AS EndTimestamp, + ExpBonusPercent, + MpBonusPercent + FROM ExpMpBonusEvents + LIMIT 1 + )"; + std::unique_ptr stmt(m_con->prepareStatement(query)); + std::unique_ptr res(stmt->executeQuery()); + + if (res->next()) + { + Main::Structures::ExpMpBonusInfo info; + info.startDate = res->getUInt("StartTimestamp"); + info.endDate = res->getUInt("EndTimestamp"); + info.expBonusPercent = res->getUInt("ExpBonusPercent"); + info.mpBonusPercent = res->getUInt("MpBonusPercent"); + return info; + } + else + { + const std::string insertQuery = R"( + INSERT INTO ExpMpBonusEvents (StartDate, EndDate, ExpBonusPercent, MpBonusPercent) + VALUES (FROM_UNIXTIME(0), FROM_UNIXTIME(0), 0, 0) + )"; + std::unique_ptr insertStmt(m_con->prepareStatement(insertQuery)); + insertStmt->executeUpdate(); + return Main::Structures::ExpMpBonusInfo{ 0, 0, 0, 0 }; + } + } + catch (const sql::SQLException& e) + { + ::Utils::Logger::log("MariaDB exception: " + std::string(e.what()), Utils::LogType::Error, "PersistentDatabase::getExpMpBonusInfo"); + } + + return std::nullopt; + } bool PersistentDatabase::updateExpMpBonusInfo(const Main::Structures::ExpMpBonusInfo& info) { @@ -1095,7 +1112,6 @@ namespace Main } } - bool PersistentDatabase::isRoomCreationDisabled(std::uint32_t playerID) { try @@ -1121,6 +1137,64 @@ namespace Main } } + std::optional PersistentDatabase::getVotekickDisabledUntil(const std::string& nickname) + { + try + { + { + std::string alterQuery = "ALTER TABLE Users ADD COLUMN IF NOT EXISTS VotekickDisabledUntil DATETIME NULL DEFAULT NULL"; + std::unique_ptr alterStmt(m_con->createStatement()); + alterStmt->execute(alterQuery); + } + + std::string selectQueryStr = "SELECT VotekickDisabledUntil FROM Users WHERE Nickname = ?"; + std::unique_ptr stmt(m_con->prepareStatement(selectQueryStr)); + stmt->setString(1, nickname); + + std::unique_ptr res(stmt->executeQuery()); + if (res->next()) + { + const std::string disabledUntil = res->getString("VotekickDisabledUntil").c_str(); + return disabledUntil; + } + return std::nullopt; + } + catch (const sql::SQLException& e) + { + ::Utils::Logger::log("MariaDB exception: " + std::string(e.what()), Utils::LogType::Error, "PersistentDatabase::getVotekickDisabledUntil"); + return std::nullopt; + } + } + + bool PersistentDatabase::isVotekickDisabled(std::uint32_t playerID) + { + try + { + { + std::string alterQuery = "ALTER TABLE Users ADD COLUMN IF NOT EXISTS VotekickDisabledUntil DATETIME NULL DEFAULT NULL"; + std::unique_ptr alterStmt(m_con->createStatement()); + alterStmt->execute(alterQuery); + } + + std::unique_ptr stmt(m_con->prepareStatement("SELECT VotekickDisabledUntil FROM Users WHERE AccountID = ?")); + stmt->setUInt(1, playerID); + + std::unique_ptr res(stmt->executeQuery()); + if (res->next()) + { + const std::string disabledUntil = res->getString("VotekickDisabledUntil").c_str(); + auto const time = std::chrono::utc_clock::now(); + return disabledUntil > std::format("{:%Y-%m-%d %X}", time); + } + return false; + } + catch (const sql::SQLException& e) + { + ::Utils::Logger::log("MariaDB exception: " + std::string(e.what()), Utils::LogType::Error, "PersistentDatabase::isVotekickDisabled"); + return false; + } + } + bool PersistentDatabase::unbanPlayer(const std::string& nickname) { try @@ -1194,7 +1268,7 @@ namespace Main item.serialInfo.itemCreationDate = static_cast<__time32_t>(resultSet->getInt64("creationDate")); item.serialInfo.itemNumber = ++itemNum; - itemNumbersToUpdate.emplace_back(std::pair{ rowId, item.serialInfo.itemNumber }); + itemNumbersToUpdate.emplace_back(std::pair{ rowId, static_cast(item.serialInfo.itemNumber) }); const std::uint64_t itemDuration_s = static_cast(resultSet->getInt64("ItemDuration")); item.expirationDate = (itemDuration_s <= 2) @@ -1414,11 +1488,7 @@ namespace Main { try { - const std::string queryStr = - "UPDATE UserItems " - "SET Stocks = ? " - "WHERE AccountID = ? AND ItemNumber = ?"; - + const std::string queryStr = "UPDATE UserItems SET Stocks = ? WHERE AccountID = ? AND ItemNumber = ?"; std::unique_ptr stmt(m_con->prepareStatement(queryStr)); stmt->setUInt(1, newStock); stmt->setUInt(2, accountID); @@ -1428,7 +1498,6 @@ namespace Main } catch (const sql::SQLException& e) { - // Log any errors that occur ::Utils::Logger::log("MariaDB exception: " + std::string(e.what()), Utils::LogType::Error, "PersistentDatabase::updateItemStock"); return false; } @@ -1931,6 +2000,57 @@ namespace Main } } + void PersistentDatabase::reduceDurability(std::uint32_t accountId, const std::vector& equippedItems) + { + try + { + std::string updateQueryStr = "UPDATE UserItems SET Durability = ? WHERE AccountID = ? AND ItemNumber = ? AND ItemDuration = 0"; + std::unique_ptr stmt(m_con->prepareStatement(updateQueryStr)); + + for (const auto& currentEquippedItem : equippedItems) + { + const auto currentItemBaseDurability = Main::CdbUtils::getItemDurability(currentEquippedItem.id); + + if (!currentItemBaseDurability || *currentItemBaseDurability == 0) + continue; + + const std::uint32_t reduction = (*currentItemBaseDurability / 100) * 1; + + const std::uint32_t newDurability = (*currentItemBaseDurability > reduction) + ? (*currentItemBaseDurability - reduction) + : 0; + + stmt->setUInt(1, newDurability); + stmt->setUInt(2, accountId); + stmt->setUInt(3, currentEquippedItem.serialInfo.itemNumber); + stmt->executeUpdate(); + } + } + catch (const sql::SQLException& e) + { + ::Utils::Logger::log(std::string("SQL Error: ") + e.what(), Utils::LogType::Error,"PersistentDatabase::reduceDurability"); + } + } + + void PersistentDatabase::updateItemDurability(std::uint32_t accountId, std::uint32_t itemNumber, std::uint32_t newDurability) + { + try + { + const std::string updateQueryStr = + "UPDATE UserItems SET Durability = ? WHERE AccountID = ? AND ItemNumber = ?"; + + std::unique_ptr stmt(m_con->prepareStatement(updateQueryStr)); + + stmt->setUInt(1, newDurability); + stmt->setUInt(2, accountId); + stmt->setUInt(3, itemNumber); + stmt->executeUpdate(); + } + catch (const sql::SQLException& e) + { + ::Utils::Logger::log(std::string("SQL Error: ") + e.what(), Utils::LogType::Error, "PersistentDatabase::updateItemDurability"); + } + } void PersistentDatabase::updatePlayerStats(std::uint32_t accountId, const Main::Structures::AccountInfo& updatedAccountInfo) { @@ -2035,6 +2155,75 @@ namespace Main } } + bool PersistentDatabase::updateVotekickDisabledUntil(const std::string& nickname, const std::string& until) + { + try + { + { + std::string alterQuery = "ALTER TABLE Users ADD COLUMN IF NOT EXISTS VotekickDisabledUntil DATETIME NULL DEFAULT NULL"; + std::unique_ptr alterStmt(m_con->createStatement()); + alterStmt->execute(alterQuery); + } + + std::string updateQuery = "UPDATE Users SET VotekickDisabledUntil = ? WHERE Nickname = ?"; + std::unique_ptr updateStmt(m_con->prepareStatement(updateQuery)); + updateStmt->setString(1, until); + updateStmt->setString(2, nickname); + + if (updateStmt->executeUpdate() == 0) + { + ::Utils::Logger::log("Update failed: No rows affected.", Utils::LogType::Warning, "PersistentDatabase::updateVotekickDisabledUntil"); + return false; + } + + return true; + } + catch (const sql::SQLException& e) + { + ::Utils::Logger::log("MariaDB exception: " + std::string(e.what()), Utils::LogType::Error, "PersistentDatabase::updateVotekickDisabledUntil"); + return false; + } + } + + bool PersistentDatabase::resetVotekickDisabledUntil(const std::string& nickname) + { + try + { + { + std::string alterQuery = "ALTER TABLE Users ADD COLUMN IF NOT EXISTS VotekickDisabledUntil DATETIME NULL DEFAULT NULL"; + std::unique_ptr alterStmt(m_con->createStatement()); + alterStmt->execute(alterQuery); + } + + std::string selectQueryStr = "SELECT AccountID FROM Users WHERE Nickname = ?"; + std::unique_ptr selectStmt(m_con->prepareStatement(selectQueryStr)); + selectStmt->setString(1, nickname); + + std::unique_ptr resultSet(selectStmt->executeQuery()); + if (!resultSet->next()) + { + return false; + } + + const std::uint32_t accountID = resultSet->getUInt("AccountID"); + std::string updateQueryStr = "UPDATE Users SET VotekickDisabledUntil = 0 WHERE AccountID = ?"; + std::unique_ptr updateStmt(m_con->prepareStatement(updateQueryStr)); + updateStmt->setUInt(1, accountID); + + if (updateStmt->executeUpdate() == 0) + { + return false; + } + return true; + } + catch (const sql::SQLException& e) + { + ::Utils::Logger::log("MariaDB exception: " + std::string(e.what()), Utils::LogType::Error, "PersistentDatabase::resetVotekickDisabledUntil"); + return false; + } + } + + bool PersistentDatabase::updateRoomCreationDisabledUntil(const std::string& nickname, const std::string& until) { try @@ -2432,7 +2621,6 @@ namespace Main uint32_t currentMaxInventory = resGetMaxInventory->getUInt("MaxInventory"); - // Reject if adding spaceToAdd would go over 1000 if (currentMaxInventory + spaceToAdd > 1000) { return false; @@ -2947,6 +3135,7 @@ namespace Main query->setUInt(1, accountId); query->setUInt64(2, mailbox.timestamp); // uniqueId (3) ignored for now + query->setUInt64(3, 0); query->setString(4, senderNickname); query->setString(5, mailbox.message); query->setBoolean(6, false); diff --git a/MainServer/src/Persistence/MainScheduler.cpp b/MainServer/src/Persistence/MainScheduler.cpp index f82163de..c27912ec 100644 --- a/MainServer/src/Persistence/MainScheduler.cpp +++ b/MainServer/src/Persistence/MainScheduler.cpp @@ -80,7 +80,6 @@ namespace Main m_databaseCallbacks.erase(accountId); m_databaseCallbacksIncremental.erase(accountId); - m_incrementalDifferentiationKey = 0; } }; } diff --git a/MainServer/src/Structures/Item/MainEquippedItem.cpp b/MainServer/src/Structures/Item/MainEquippedItem.cpp index 6e0eb5fe..3989c1f4 100644 --- a/MainServer/src/Structures/Item/MainEquippedItem.cpp +++ b/MainServer/src/Structures/Item/MainEquippedItem.cpp @@ -1,6 +1,8 @@ +#ifndef MAIN_EQUIPPED_ITEM_CPP +#define MAIN_EQUIPPED_ITEM_CPP -#include #include +#include #include "../../../include/Structures/Item/MainItem.h" #include "../../../include/Detail/CdbUtils.h" #include "../../../include/Structures/Item/MainEquippedItem.h" @@ -12,8 +14,18 @@ namespace Main namespace Structures { EquippedItem::EquippedItem(const Main::Structures::Item& item, std::uint64_t aid) - : id{ item.itemId.itemId }, expirationDate{ item.expirationDate }, serialInfo{ item.serialInfo }, durability{ item.durability } - , energy{ item.energy }, isSealed { item.isSealed }, sealLevel{item.sealLevel}, experienceEnhancement{item.experienceEnhancement} + : id{ item.itemId.itemId } +#ifdef _WIN32 + , expirationDate{ static_cast<__time32_t>(item.expirationDate) } +#else + , expirationDate{ static_cast(item.expirationDate) } +#endif + , serialInfo{ item.serialInfo } + , durability{ item.durability } + , energy{ item.energy } + , isSealed{ item.isSealed } + , sealLevel{ item.sealLevel } + , experienceEnhancement{ item.experienceEnhancement } , mpEnhancement{ item.mpEnhancement } { type = CdbUtils::getItemType(item.itemId.itemId).value_or(static_cast(-1)); @@ -31,7 +43,8 @@ namespace Main } } } - } } +#endif + diff --git a/Microvolts-Emulator-V2.sln b/Microvolts-Emulator-V2.sln deleted file mode 100644 index b58ee55e..00000000 --- a/Microvolts-Emulator-V2.sln +++ /dev/null @@ -1,74 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.3.32922.545 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Microvolts-Emulator-V2", "Microvolts-Emulator-V2\Microvolts-Emulator-V2.vcxproj", "{4FAE3DF3-2DE6-4E13-9D69-6BFD36732128}" - ProjectSection(ProjectDependencies) = postProject - {B17B4C2B-A318-43AB-BD0B-2A0BB9B4470D} = {B17B4C2B-A318-43AB-BD0B-2A0BB9B4470D} - EndProjectSection -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "Common\Common.vcxproj", "{B17B4C2B-A318-43AB-BD0B-2A0BB9B4470D}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AuthServer", "AuthServer\AuthServer.vcxproj", "{9B06C7AC-41F2-459F-B8DE-1404C0DDD262}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "MainServer", "MainServer\MainServer.vcxproj", "{FB8CD300-B47A-498D-9038-7D0DCFFE695F}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CastServer", "CastServer\CastServer.vcxproj", "{8E0E7FF9-66AC-4F7D-9EE6-A67DB4E27B24}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4FAE3DF3-2DE6-4E13-9D69-6BFD36732128}.Debug|x64.ActiveCfg = Debug|x64 - {4FAE3DF3-2DE6-4E13-9D69-6BFD36732128}.Debug|x64.Build.0 = Debug|x64 - {4FAE3DF3-2DE6-4E13-9D69-6BFD36732128}.Debug|x86.ActiveCfg = Debug|Win32 - {4FAE3DF3-2DE6-4E13-9D69-6BFD36732128}.Debug|x86.Build.0 = Debug|Win32 - {4FAE3DF3-2DE6-4E13-9D69-6BFD36732128}.Release|x64.ActiveCfg = Release|x64 - {4FAE3DF3-2DE6-4E13-9D69-6BFD36732128}.Release|x64.Build.0 = Release|x64 - {4FAE3DF3-2DE6-4E13-9D69-6BFD36732128}.Release|x86.ActiveCfg = Release|Win32 - {4FAE3DF3-2DE6-4E13-9D69-6BFD36732128}.Release|x86.Build.0 = Release|Win32 - {B17B4C2B-A318-43AB-BD0B-2A0BB9B4470D}.Debug|x64.ActiveCfg = Debug|x64 - {B17B4C2B-A318-43AB-BD0B-2A0BB9B4470D}.Debug|x64.Build.0 = Debug|x64 - {B17B4C2B-A318-43AB-BD0B-2A0BB9B4470D}.Debug|x86.ActiveCfg = Debug|Win32 - {B17B4C2B-A318-43AB-BD0B-2A0BB9B4470D}.Debug|x86.Build.0 = Debug|Win32 - {B17B4C2B-A318-43AB-BD0B-2A0BB9B4470D}.Release|x64.ActiveCfg = Release|x64 - {B17B4C2B-A318-43AB-BD0B-2A0BB9B4470D}.Release|x64.Build.0 = Release|x64 - {B17B4C2B-A318-43AB-BD0B-2A0BB9B4470D}.Release|x86.ActiveCfg = Release|Win32 - {B17B4C2B-A318-43AB-BD0B-2A0BB9B4470D}.Release|x86.Build.0 = Release|Win32 - {9B06C7AC-41F2-459F-B8DE-1404C0DDD262}.Debug|x64.ActiveCfg = Debug|x64 - {9B06C7AC-41F2-459F-B8DE-1404C0DDD262}.Debug|x64.Build.0 = Debug|x64 - {9B06C7AC-41F2-459F-B8DE-1404C0DDD262}.Debug|x86.ActiveCfg = Debug|Win32 - {9B06C7AC-41F2-459F-B8DE-1404C0DDD262}.Debug|x86.Build.0 = Debug|Win32 - {9B06C7AC-41F2-459F-B8DE-1404C0DDD262}.Release|x64.ActiveCfg = Release|x64 - {9B06C7AC-41F2-459F-B8DE-1404C0DDD262}.Release|x64.Build.0 = Release|x64 - {9B06C7AC-41F2-459F-B8DE-1404C0DDD262}.Release|x86.ActiveCfg = Release|Win32 - {9B06C7AC-41F2-459F-B8DE-1404C0DDD262}.Release|x86.Build.0 = Release|Win32 - {FB8CD300-B47A-498D-9038-7D0DCFFE695F}.Debug|x64.ActiveCfg = Debug|x64 - {FB8CD300-B47A-498D-9038-7D0DCFFE695F}.Debug|x64.Build.0 = Debug|x64 - {FB8CD300-B47A-498D-9038-7D0DCFFE695F}.Debug|x86.ActiveCfg = Debug|Win32 - {FB8CD300-B47A-498D-9038-7D0DCFFE695F}.Debug|x86.Build.0 = Debug|Win32 - {FB8CD300-B47A-498D-9038-7D0DCFFE695F}.Release|x64.ActiveCfg = Release|x64 - {FB8CD300-B47A-498D-9038-7D0DCFFE695F}.Release|x64.Build.0 = Release|x64 - {FB8CD300-B47A-498D-9038-7D0DCFFE695F}.Release|x86.ActiveCfg = Release|Win32 - {FB8CD300-B47A-498D-9038-7D0DCFFE695F}.Release|x86.Build.0 = Release|Win32 - {8E0E7FF9-66AC-4F7D-9EE6-A67DB4E27B24}.Debug|x64.ActiveCfg = Debug|x64 - {8E0E7FF9-66AC-4F7D-9EE6-A67DB4E27B24}.Debug|x64.Build.0 = Debug|x64 - {8E0E7FF9-66AC-4F7D-9EE6-A67DB4E27B24}.Debug|x86.ActiveCfg = Debug|Win32 - {8E0E7FF9-66AC-4F7D-9EE6-A67DB4E27B24}.Debug|x86.Build.0 = Debug|Win32 - {8E0E7FF9-66AC-4F7D-9EE6-A67DB4E27B24}.Release|x64.ActiveCfg = Release|x64 - {8E0E7FF9-66AC-4F7D-9EE6-A67DB4E27B24}.Release|x64.Build.0 = Release|x64 - {8E0E7FF9-66AC-4F7D-9EE6-A67DB4E27B24}.Release|x86.ActiveCfg = Release|Win32 - {8E0E7FF9-66AC-4F7D-9EE6-A67DB4E27B24}.Release|x86.Build.0 = Release|Win32 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {5F8D7F8C-EF79-4C7F-9EB3-F3DA98167C5B} - EndGlobalSection -EndGlobal diff --git a/Microvolts-Emulator-V2/Microvolts-Emulator-V2.vcxproj b/Microvolts-Emulator-V2/Microvolts-Emulator-V2.vcxproj deleted file mode 100644 index 47927632..00000000 --- a/Microvolts-Emulator-V2/Microvolts-Emulator-V2.vcxproj +++ /dev/null @@ -1,138 +0,0 @@ - - - - - Debug - Win32 - - - Release - Win32 - - - Debug - x64 - - - Release - x64 - - - - 16.0 - Win32Proj - {4fae3df3-2de6-4e13-9d69-6bfd36732128} - MicrovoltsEmulatorV2 - 10.0 - - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - Application - true - v143 - Unicode - - - Application - false - v143 - true - Unicode - - - - - - - - - - - - - - - - - - - - - ..\ExternalLibraries\vcpkg\vcpkg_installed - - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - true - true - - - - - Level3 - true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - - - - - Level3 - true - true - true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - true - true - - - - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index a903fe7d..0294a5bf 100644 --- a/README.md +++ b/README.md @@ -1,54 +1,61 @@ - -# Microvolts Emulator 2.0 is available! - -🚀 After three years of continuous development—and one intense year focused on the 1.0 to 2.0 refactor—the latest version of the emulator is now publicly available! -The entire project has been thoroughly refactored to improve clarity, maintainability, and overall structure. Numerous bugs have been fixed, and most previously missing features are now fully functional. - -🙏 A special thanks to the community—especially those who actively contributed through the ToyBattles project—for your invaluable support, testing, and feedback throughout the development process. - -**Microvolts Emulator 2.0** specifically targets **Microvolts client version 1.1** (Surge). - - - - - + ## Appendix - -### 1. 📘 Project Overview +### 1. Project Overview - [1.1 What is this project about?](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/intro.md) + - [1.1.1 ToyBattles](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/intro.md) + - [1.1.1.1 Team Members](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/intro.md) + - [1.1.2 About this emulator](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/intro.md) + - [1.1.3 Community Driven - A few videos](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/intro.md) - [1.2 Overview of the different projects (MainServer, CastServer, AuthServer, Common)](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/intro2.md) -### 2. 🔄 Version History +### 2. Version History - [2.1 Changelog from version 1.0 to version 2.0](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/changelog1.md) + - [2.1.1 Complete code refactoring](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/changelog1.md) + - [2.1.2 New Features](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/changelog1.md) + - [2.1.3 Miscellaneous](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/changelog1.md) -### 3. ⚙️ Installation & Setup +### 3. Installation & Setup - [3.1 Requirements & Installation](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/requirements_installation.md) + - [3.1.1 Requirements](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/requirements_installation.md) + - [3.1.2.1 Automatic Installation: Automatic Linux Setup](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/requirements_installation.md) + - [3.1.2.2 Manual Installation: Windows](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/requirements_installation.md) + - [3.1.2.3 Manual Installation: Linux](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/requirements_installation.md) - [3.2 Setting up the emulator](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/setting_up.md) - [3.3 Setting up the database](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/database_setup.md) + - [3.3.1 Windows Setup](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/database_setup.md) + - [3.3.2 Linux Setup](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/database_setup.md) - [3.4 Setting up the client](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/client_setup.md) - -### 4. 🧪 Configuration Examples + - [3.4.1 Downloading the Client](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/client_setup.md) + - [3.4.2 Installing the `cgd.dip` archive with localhost](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/client_setup.md) + +### 4. Configuration Examples - [4.1 Config setup for localhost](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/example_localhost.md) - [4.2 Config setup for VPS](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/example_vps.md) - [4.3 Config setup for multiple servers across different VPSs](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/example_multiple_vps.md) -### 5. 🗃️ Database +### 5. Database - [5.1 Tour of database tables](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/database_tour.md) -### 6. 🌐 Website API +### 6. Website API - [6.1 Website API overview and communication with MainServer](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/website_info.md) -### 7. 🚀 Updater & Client Tools +### 7. Updater & Client Tools - [7.1 The Updater: How the launcher retrieves updates](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/updater_overview.md) + - [7.1.1 Overview](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/updater_overview.md) + - [7.1.2 Creating Update Packages](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/updater_overview.md) + - [7.1.3 Using the Updater](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/updater_overview.md) - [7.2 Changing the client version (using the client version tool)](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/client_version_tool.md) - [7.3 Changing the CGD password (using the password tool)](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/password_updater.md) -### 8. 🐞 Reporting Issues +### 8. Reporting Issues - [8.1 How to create bug reports via GitHub Issues](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/reporting_issues.md) - -### 9. 🙌 Acknowledgements + - [8.1.1 Types of Issues](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/reporting_issues.md) + - [8.1.2 How to Report an Issue](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/reporting_issues.md) + - [8.1.3 Found a fix? Feel free to open a PR!](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/reporting_issues.md) + +### 9. Acknowledgements - [9.1 Credits and community contributions](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/thanks.md) -### 10. 🔮 Future steps +### 10. Future steps - [What's next?](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/whats_next.md) diff --git a/Setup/config.ini b/Setup/config.ini index b548c1f9..cc0e0992 100644 --- a/Setup/config.ini +++ b/Setup/config.ini @@ -1,32 +1,32 @@ [AuthServer] -LocalIp = # Your local Ipv4 IP here -Ip = # public IP of the server (127.0.0.1 or VPS ip) +LocalIp = 127.0.0.1 +Ip = 127.0.0.1 Port = 13000 [MainServer_1] -LocalIp = # Your local Ipv4 IP here -Ip = # public IP of the server (127.0.0.1 or VPS ip) +LocalIp = 127.0.0.1 +Ip = 127.0.0.1 Port = 13005 IpcPort = 14005 IsPublic = true [CastServer_1] -LocalIp = # Your local Ipv4 IP here -Ip = # public IP of the server (127.0.0.1 or VPS ip) +LocalIp = 127.0.0.1 +Ip = 127.0.0.1 Port = 13006 IpcPort = 14006 [Database] -LocalIp = # Your local Ipv4 IP here -Ip = # public IP of the server (127.0.0.1 or VPS ip) +LocalIp = 127.0.0.1 +Ip = 127.0.0.1 Port = 3305 DatabaseName = microvolts-db Username = root -PasswordEnvironmentName = # system environment variable name that contains the password goes here +PasswordEnvironmentName = MV_DB_PW [Website] Ip = 127.0.0.1 # or any website IP you may have Port = 8080 [Client] -ClientVersion = 1.1.1 \ No newline at end of file +ClientVersion = 1.1.1 diff --git a/doc/changelog1.md b/doc/changelog1.md index 8ed5cd21..cb3b3ee7 100644 --- a/doc/changelog1.md +++ b/doc/changelog1.md @@ -1,16 +1,20 @@ # 2.1 Changelog from version 1.0 to version 2.0 -A lot of things changed and the list is simply too big to fit in a page. I will try to focus on the most important aspects to give an idea how much work was put in the last year (thanks to the community who helped testing too, of course!) +A lot of things changed and the list is simply too big to fit in a page. Let's try to focus on the most important aspects to give an idea how much work was put in the last years (thanks to the community who helped testing too, of course!) ## 2.1.1 Complete code refactoring One of the main goals of version 2.0 was to clean up the entire codebase — that includes the Main Server, Cast Server, Auth Server, and the Common library. The code from version 1.0 worked, but it had a lot of problems when it came to maintainability and readability. It was messy in some places, hard to follow, and had some tricky lifetime issues, especially with how ASIO was used. It just wasn’t easy to work with, especially for anyone new coming into the project. +### Cross Platform +Finally, the project is starting to work on other platforms too! +Currently, the project has been tested with Windows (MSVC) and Linux (GCC). + ### Code Organization & Readability Version 2.0 changes all that. Things are now much more organized, and the code is way easier to read. If you’re opening the project for the first time, it should be a lot more approachable now. -In 1.0, the logic was often all over the place and pretty hard to understand without spending a lot of time digging. With this new version, I tried to make everything feel more structured and easier to follow, so it’s not just me who can work on this — but anyone who’s interested. +In 1.0, the logic was often all over the place and pretty hard to understand without spending a lot of time digging. With this new version, we tried to make everything feel more structured and easier to follow, so it’s not just us who can work on this - but anyone who’s interested. ### Networking performance Version 2.0 offers better networking performance with ASIO. Also, most of the server handlers & session management was rewritten with optimization in mind. Many data strucutres were changed: @@ -21,7 +25,7 @@ Version 2.0 offers better networking performance with ASIO. Also, most of the se ### Security improvements & server stability The new version also had security improvements in mind. - Multiple exploits prevention are now available in this version: prevented various exploits such as room crashes, duplicate items, items disappearing, etc. -- Anti-SYS flooding system: there were issues with SYS flooding in the ToyBattles project. I thus decided to add a simple anti SYS flooding system. +- Anti-SYS flooding system: there were issues with SYS flooding in the ToyBattles project. We thus decided to add a simple anti SYS flooding system. - No more constant server crashes: a lot of issues related to bad memory management were fixed. The servers in version 2.0 are now much, much more stable and with less crashes than ever! - Added 2FA authentication for graded accounts (mandatory), and for normal accounts (if wanted). @@ -46,7 +50,7 @@ Almost all bugs that existed in version 1.0 were fixed: ...and much more! ## 2.1.2 New Features -I also added a lot of new features in version 2.0! +We also added a lot of new features in version 2.0! - Arena Mode: this is a mode inspired by ToyHeroes, but fully server sided in this project. To enable it, all you need is to be in a room with the "Academy Training Ground" map. You can find examples of Arena Mode on youtube. - Assassin Mode: this is a completely new, server-sided mode. It works on Elimination and can be activated while you're the host in a room by using the chat command `/setassassinmode`. Make sure to try it out! - Gift System was now added. Game masters can use the `/sendgift` command to send items to players. @@ -74,7 +78,7 @@ I also added a lot of new features in version 2.0! ## Miscellaneous ### 2.1.3 Database Changes -Back in version 1.0, the emulator relied on SQLite — which worked, but wasn’t really ideal for a more serious setup. With version 2.0, everything has been moved over to MariaDB, which is a big step up in terms of flexibility and reliability. +Back in version 1.0, the emulator relied on SQLite - which worked, but wasn’t really ideal for a more serious setup. With version 2.0, everything has been moved over to MariaDB, which is a big step up in terms of flexibility and reliability. This switch meant a full refactor of how database operations are handled. The cool part? Now you can have multiple servers (Auth, Main, Cast, etc.) all sharing the same database — something that wasn’t really possible or safe before. @@ -84,7 +88,7 @@ Another big change: the client updater is now open source. That means you can us ### 2.1.5 External Admin Panel Support Version 2.0 also introduces support for external admin panels. In other words, the Main Server now has an API that allows communication with a website or web-based admin interface. -Everything is protected with proper authentication — including user grades and JWT tokens — so only authorized users can access it. Through this system, you can do things like kick, ban, or mute players directly from your panel, without needing to log into the server manually. It’s all covered in the “Website API” chapter coming up. +Everything is protected with proper authentication, including user grades and JWT tokens, so only authorized users can access it. Through this system, you can do things like kick, ban, or mute players directly from your panel, without needing to log into the server manually. It’s all covered in the “Website API” chapter coming up. ## Next diff --git a/doc/client_setup.md b/doc/client_setup.md index bc792e78..8deee30a 100644 --- a/doc/client_setup.md +++ b/doc/client_setup.md @@ -1,19 +1,25 @@ # 3.4 Setting up the client -This is the final step — you're almost there! +This is the final step so we're almost there. We now need to make sure the client is configured to connect to your local server. -### Download the client -You can use any client version newer than Surge, but I personally recommend the ToyBattles client. Here are the download links: -- Mediafire: https://www.mediafire.com/file/4flrsczyh74a0yi/ToyBattles.zip/file -- Google Drive: https://drive.google.com/file/d/1hTLwJy3JDajafw8ogdEIM-obCFnR2_Zq/view +### 3.4.1 Download the client +You can technically use any client version newer than Surge, but I recommend the ToyBattles client. Here's the download link: https://mega.nz/file/fR13xIJY#fYM0yGEZcaqz3MbhD5tqBaUEByIX5jHUxYV9GniXeZU -### Install the `cgd.dip` archive with localhost IPs +Using the ToyBattles client **is highly recommended** since it will already contain some client modifications that allow specific features like the Gamble System, Custom error messages and correct map IDs. + +**Note**: If you still decide to use the original Surge client, you will need to update the GameMaps enum since this branch is targeted towards a modified version of the client that uses a different order of the maps (the one given above). +Refer to the branch `mv.1_1` to get the correct GameMaps enum for the original Surge client. +Also, the original Surge client will not have the correct error messages in certain cases and some UI names will be wrong when used with this server emulator. + +### 3.4.2 Install the `cgd.dip` archive with localhost IPs Next, download the latest cgd.dip archive from the link below. This file is already set up to point to 127.0.0.1 (localhost) and includes all necessary client data: https://www.mediafire.com/file/5uw65fwps66fsll/cgd.dip/file This will allow your client to connect to your localhost (127.0.0.1) server. -✅ Note: This archive is pre-configured to work with the provided config.ini file. You don’t need to change the ports — everything should match up automatically. +**Note**: This archive is pre-configured to work with the provided config.ini file. You don’t need to change the ports, everything should match up automatically. + +Basically the local `cgd.dip` is needed by the client to show correct messages/UI/stats, while the server side `config.ini` contains an unpacked version of this file and is used for security purposes (ie. the server only trusts the stats given in its unpacked cgd archive, and not the ones provided by the client). When done, make sure to replace the `cgd.dip` you have in your game folder / `data` with this one. (Take a backup of the original one just in case) @@ -25,17 +31,17 @@ To launch the game, you have two options: # Final Checklist: Starting Everything Up By now, you should have completed the following steps: -- ✅ Compiled all the executables (Common, MainServer, AuthServer, and CastServer) -- ✅ Launched each server executable without seeing any red error messages -- ✅ Set up and started your MariaDB database -- ✅ Edited your config.ini file so that all IPs point to 127.0.0.1 (localhost), if you're using the cgd.dip file from above -- ✅ Downloaded and started the client - +- Compiled all the executables (Common, MainServer, AuthServer, and CastServer) +- Launched each server executable without seeing any red error messages +- Set up and started your MariaDB database +- Installed the `cgd.dip` from above and replaced it in your client's `data` folder +- Started your game client. + If everything’s in place and running correctly, you should be able to log in using the following test credentials: -Username: test -Password: test +Username: `test` +Password: `test` ## Next [4.1 Config setup for localhost](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/example_localhost.md) diff --git a/doc/database_setup.md b/doc/database_setup.md index 2f40457a..05984ba3 100644 --- a/doc/database_setup.md +++ b/doc/database_setup.md @@ -1,17 +1,23 @@ # 3.3 Setting up the database -Starting from version 2.0, I introduce MariaDB. This is much better since it allows you to have multiple regionals servers connect to the same, central database. +Starting from version 2.0, we introduce MariaDB. This is much better since it allows you to have multiple regionals servers connect to the same, central database. +This section shows setup for both Windows and Linux. +## 3.3.1 Windows Setup +Before running the servers, you’ll need a MariaDB database properly configured with the right tables and data. Here's how to do it step by step: -## How to Set Up the MariaDB Database -Before running the servers, you’ll need a MariaDB database properly configured with the right tables and data. Don’t worry — it’s not hard. Here's how to do it step by step: +### Step 0: Install MariaDB +If you haven’t already installed MariaDB: + +- Go to https://mariadb.org/download/ +- Download the version that matches your OS +- During installation, make sure to remember the root password (you’ll need it shortly) -### Step 0 (Optional but recommended for an easy setup) +### Step 0.1 (Optional but recommended for an easy setup) - Find `my.ini` inside your MariaDB folder. (Usually it can be found in `"C:\Program Files\MariaDB 11.6\data\my.ini"` - Replace its contents with the following: ```cpp [mysqld] -skip-grant-tables datadir="C:/Program Files/MariaDB 11.6/data" port=3305 innodb_buffer_pool_size=1967M @@ -23,12 +29,9 @@ plugin-dir="C:/Program Files/MariaDB 11.6/lib/plugin" Of course, make sure the paths are correct for you and that you have the necessary things installed (like innoDB). -### Step 1: Install MariaDB -If you haven’t already installed MariaDB: -- Go to https://mariadb.org/download/ -- Download the version that matches your OS -- During installation, make sure to remember the root password (you’ll need it shortly) +### Step 1: Start mariaDB +Simply open a terminal and type `net start mariadb`. By default this will run on port 3306 (unless you did the optional step 0.1, in which case the port will be 3305), so make sure to use the correct database port on the `config.ini` file! ### Step 2: Create the Database Once MariaDB is installed: @@ -44,7 +47,8 @@ You’ve been provided with a file called `microvolts-db.`. This file contains t To import it: #### Option A – Using Command Line -- `mysql -u root -p microvolts-db < path/to/microvolts-db.md` +- `mysql -u root -p microvolts-db < path/to/microvolts-db.sql` + (You’ll be prompted to enter your root password) #### Option B – Using GUI (e.g., HeidiSQL) @@ -58,7 +62,6 @@ For security, the server expects your database password to be stored in an envir For example, if in `setup.ini` you wrote: `PasswordEnvironmentName = MICRO_DB_PW` - On Windows: - Press Win + S → Search for “Environment Variables” - Click “Edit the system environment variables” @@ -68,8 +71,55 @@ On Windows: Done! -### Step 5: Start mariaDB -Simply open a terminal and type `net start mariadb`. By default this will run on port 3306, so make sure to use the correct database port on the `config.ini` file! +## 3.3.2 Linux Setup +### Step 0: Install MariaDB +- Debian/Ubuntu: +```cpp +sudo apt update +sudo apt install mariadb-server mariadb-client +``` + +- Fedora/CentOS/RHEL: `sudo dnf install mariadb-server mariadb` + +Start and enable the service: +```cpp +sudo systemctl start mariadb +sudo systemctl enable mariadb +``` + +### Step 0.1 (Optional but recommended for an easy setup) +- Edit the MariaDB configuration file (usually found at `/etc/mysql/mariadb.conf.d/50-server.cnf or /etc/my.cnf`) +- Add or modify the following sections: +```cpp +[mysqld] +datadir=/var/lib/mysql +port=3305 +innodb_buffer_pool_size=1967M + +[client] +port=3305 +``` +Make sure paths correspond to your installation. `datadir` generally defaults to `/var/lib/mysql` + + +### Step 1: Start the service +`sudo systemctl start mariadb` +You can check if it's running with `sudo systemctl status mariadb` + +### Step 2: Create the database +- `sudo mariadb -u root` +- `CREATE DATABASE microvolts-db;` +- `EXIT;` + +### Step 3: Import the tables +`mysql -u root -p microvolts-db < /path/to/microvolts-db.sql` (you can find `microvolts-db` in this repository at the root directory) + +### Step 4: Set password via environment variable +`export MICRO_DB_PW=your_db_password_here` + +If you want it to be permanent: +- For bash: `echo 'export MICRO_DB_PW=your_db_password_here' >> ~/.bashrc` +- For zsh: `echo 'export MICRO_DB_PW=your_db_password_here' >> ~/.zshsrc` ## Next diff --git a/doc/database_tour.md b/doc/database_tour.md index b9aaa561..fbd185df 100644 --- a/doc/database_tour.md +++ b/doc/database_tour.md @@ -2,18 +2,18 @@ Version 2.0 of the emulator brings a major upgrade: the database backend has been moved **from SQLite to MariaDB.** -While SQLite was a simple and lightweight solution, it quickly became a bottleneck — especially when multiple servers needed to access the database at the same time. +While SQLite was a simple and lightweight solution, it quickly became a bottleneck, especially when multiple servers needed to access the database at the same time. It also lacked advanced features like proper user management, networked access, and better concurrency handling, which are essential as the project grows. That said, the overall structure of the database is **still very similar to the previous version**, with a few new additions here and there. Below is a quick overview of the most important database tables. If you're planning to contribute to the emulator, I strongly recommend **opening the database with a tool like HeidiSQL** to explore everything in detail. -This isn’t a full walkthrough — I’ll just highlight the key tables and point out some things I think are worth knowing. +This isn’t a full walkthrough: I’ll just highlight the key tables and point out some things I think are worth knowing. ### `Users` Table This table contains all information regarding a specific player: accountID, usernames, nicknames, passwords, level, kills, experience, to name a few. -- For security reasons, the password is not in plain text. Instead, you must use SHA216. -- The 2FA for graded accounts (or even normal ones if you wish) must be setup in this table. The column name is `Secret` and it must be a base32 hash (for example: `MRSG4NJVNZSDKZDS` where the original text was `ddn55nd5dr`). +- For security reasons, the password is not in plain text. Instead, you must use SHA256. +- The 2FA for graded accounts (grade >= 1) (or even normal ones if you wish) must be setup in this table. The column name is `Secret` and it must be a base32 hash (for example: `MRSG4NJVNZSDKZDS`). The 2FA is time-based, any application like Google Authenticator will work. ### `UserItems` Table This table contains all information regarding user items. Each user is identified by an accountID, and each row represents an item that a player identified by their accountID has. diff --git a/doc/intro.md b/doc/intro.md index 5742640f..a5f5cd26 100644 --- a/doc/intro.md +++ b/doc/intro.md @@ -1,7 +1,48 @@ # 1.1 What is this project about? + +## 1.1.1 ToyBattles +**ToyBattles** is a **private** MicroVolts server, completely free to play. +There's always been an official server somewhere, or other private servers like ToyHeroes, but what makes ToyBattles completely different is the free to play (for real!) nature. +There's no payment involved at all: in-game currency, RockTokens included, is obtained purely through gameplay and events. + +Apart from this, ToyBattles is a **community-driven** project. Although there's a team that handles the servers, the client and a website, every feature is tested by the players; all of the team members are also just a bunch of people with the same goal in mind, that is, providing a free alternative to an otherwise forgotten game. + +This is a glimpse of what we've accomplished through the ToyBattles projects: +- A stable and secure server emulator that can be used as a solid base for further work. +- A new map and new game modes (purely server sided). +- A lot of custom changes in the client, including but not limited to: + - Changes to the constant database (`cgd.dip`), + - Client executable patches, + - UI changes and improvements +- New accessories, sets, and parts - created by the ToyBattles team members and also by the community itself. +- A completely free to play game, where in-game currency is obtained solely through gameplay and by participating at events. +- A simple website that is used to publish news, show player status, etc. +- A custom launcher. +- An updater tool, used by the ToyBattles team to push new content. +- A custom Admin Panel that is used by team members to manage the community. +- **But most importantly, we built a fantastic community**. + +For more information you can visit the ToyBattles website at https://toybattles.net/. + +### 1.1.1.1 ToyBattles Team +Considering the efforts that the whole team put onto the project, it's fair to write about each member and how they contributed (or still contribute). + +- [SoWeBegin](https://github.com/SoWeBegin): The original author of this Emulator. Contributes to the emulator code & helps managing the ToyBattles server. +- [Mikael](https://github.com/orgs/ToyBattles/people/Mikael): An essential part of the ToyBattles project. Actively contributes to the emulator, manages the ToyBattles server, and created a bunch of essential things (like the website & admin panel) for ToyBattles. +- [Krymel](https://github.com/orgs/ToyBattles/people/Krymelte): One of the original project members, he's the one behind all the `cgd.dip` changes, additions and modifications. He's also the one who manages the updates for ToyBattles. +- [OGSapphire](https://github.com/orgs/ToyBattles/people/m1exe): Also one of the original project members, he likes to work on the artistic side. This includes (but isn't limited to) client UI changes, in-game item and UI changes, maps, and much more. +- [Ren](https://github.com/orgs/ToyBattles/people/RandomMV): Ren is more on the client side. Major client patches and UI changes are all done thanks to his work. +- Slauj: He's also on the artistic side. He's the one behind new maps but also is very knowledgeable when it gets to the client side. +- Autumn: Every community ticket is taken care by this (poor) soul. Community events? Check. Player support? Check. Moderation/Event Supporter management? Check. +- Scayla: Currently unavailable, but worth mentioning. She's been taking care of the Moderators at ToyBattles and specifically served as a community manager. +- Kixra: She mainly works on the design side. For example, the ToyBattles custom launcher is the result of her own efforts. + +*Note* Special mention to our Moderators and Event Supporters who actively support in mantaining a good relationship with the community. + +### 1.1.2 About this emulator This is the first public server emulator for MicroVolts, fully written in C++. -It all started about three years ago. I wanted to challenge myself, learn more about reverse engineering, and work on something that actually meant something to me. MicroVolts was a big part of my childhood, so I figured—why not try building an emulator for it? +It all started a few years ago. I personally wanted to challenge myself, learn more about reverse engineering, and work on something that actually meant something to me. MicroVolts was a big part of my childhood, so I figured - why not try building an emulator for it? What made things tough back then was how little help was out there. Most people who had made progress on their own emulators either kept everything to themselves or simply disappeared. No guides, no explanations, nothing. Except for one person who helped me out in the beginning, and to them I’ll always be thankful. @@ -9,20 +50,16 @@ That experience is one of the main reasons I decided to make this project open s I really hope this encourages more devs to get involved with the game and keep it alive in their own way. -## Reason for open sourceness -- Give an idea on how the emulator is written, and possibly inspire new people to create their own better version. -- Learning purposes. -- Make sure that the game stays alive even in the future. -- Allow people to further enhance the game's possibilities. +#### Emulator Demonstration +![image](https://github.com/user-attachments/assets/02fcbfa0-530a-403b-8279-72ba8193d823) -## Demonstration -This is one of the first videos I ever nade around 2 years ago, at the first stages of this emulator: +This is one of the first videos I ever made around 2 years ago, at the first stages of this emulator: https://github.com/user-attachments/assets/24b7a12a-517d-42a9-9554-fe90d8a9f5a9 There were a lot of progresses. You can find out many videos on youtube by searching for "ToyBattles", which is a community project based on this emulator. -Examples: +### 1.1.3 Community Driven - A few videos: - https://www.youtube.com/watch?v=_P41fWD7Qag - https://www.youtube.com/watch?v=3xJT4yMjj6Q - https://www.youtube.com/watch?v=qme0okRUlwE diff --git a/doc/intro2.md b/doc/intro2.md index d4335c40..0d4b58a5 100644 --- a/doc/intro2.md +++ b/doc/intro2.md @@ -1,13 +1,13 @@ # 1.2 Overview of the different projects (MainServer, CastServer, AuthServer, Common, Client) -Before introducing the requirement and the installation process, I'd like to discuss quickly what the different sub-projects of this project are, and some important stuff that you may notice once you have your setup ready. +Before introducing the requirements and the installation process, let's first discuss quickly what the different sub-projects of this project are, and some important stuff that you may notice once you have your setup ready. server-setup ## Common This is a static library (.lib) that includes shared utilities used by all the servers — Auth, Main, and Cast. -It acts as the foundation for several core features like networking (handling communication between the server and client), cryptography, CDB data structures, and other essential components that are reused across the project. +It acts as the foundation for many core features like networking (handling communication between the server and client), cryptography, CDB data structures, and other essential components that are reused across the project. ## Auth Server @@ -20,7 +20,7 @@ This setting can be configured directly in the database (we’ll cover how later ## Main Server -The MainServer is at the core of the emulator — it handles just about everything except login and most of the gameplay logic. +The MainServer is at the core of the emulator - it handles just about everything except login and most of the gameplay logic. In simple terms, it’s responsible for: @@ -44,12 +44,12 @@ The CastServer is responsible for handling most of the gameplay-related communic - Match state and updates - Item pickups and drops - Player position and movement updates -...and more +...and more. -Since this server handles real-time gameplay, it needs to be fast. For that reason, the packets sent here are not encrypted — unlike the MainServer, which deals with more sensitive data and uses encryption accordingly. +Since this server handles real-time gameplay, it needs to be fast. For that reason, the packets sent here are not encrypted - unlike the MainServer, which deals with more sensitive data and uses encryption accordingly. ## Client folder -This is where I added a few utilities to help with client modifications. More details on how to use them are covered in a later section. +This is where we added a few utilities to help with client modifications. More details on how to use them are covered in a later section. ## External Libraries This folder holds all the external dependencies used across the project, including header-only libraries. @@ -57,7 +57,7 @@ This folder holds all the external dependencies used across the project, includi When you compile the Common project, it will generate a .lib file that gets placed here under /ExternalLibraries/CommonLib. **⚠️ Important:**: Make sure to place the unpacked `cgd.dip` archive inside the cgd_original folder here. -This archive is essential for the Main Server — it contains key client data like: +This archive is essential for the Main Server and contains important client data like: - Shop item prices - Capsule contents @@ -67,6 +67,7 @@ This archive is essential for the Main Server — it contains key client data li Without it, the server won’t be able to function properly when handling anything related to (for example) game items, capsule info, etc. So always make sure you’re using the most up-to-date and correctly unpacked version of `cgd.dip`. +**Note**: This repository already provides a server-sided `cgd_original` folder, along with the corresponding client `cgd.dip` which it was extracted from. All the links to the client & `cgd.dip` folder are given in the next chapters. ## Next [2.1 Changelog from version 1.0 to version 2.0](https://github.com/SoWeBegin/MicrovoltsEmulator/blob/mv1.1_2.0/doc/changelog1.md) diff --git a/doc/reporting_issues.md b/doc/reporting_issues.md index cc72a89c..33c48268 100644 --- a/doc/reporting_issues.md +++ b/doc/reporting_issues.md @@ -1,61 +1,57 @@ -# 🛠️ 8.1 How to create bug reports via GitHub Issues +# 8.1 How to create bug reports via GitHub Issues -If you’ve found something wrong with the server emulator — whether it’s a bug, an exploit, or anything else — thank you for taking the time to report it! This helps improve the project for everyone. Please read below to make sure your report is clear and useful. +If you’ve found something wrong with the server emulator, whether it’s a bug, an exploit, or anything else, thank you for taking the time to report it! This helps improve the project for everyone. Please read below to make sure your report is clear and useful. ---- +## 8.1.1 Types of Issues -## 📂 Types of Issues +### Bugs +Something doesn’t work as expected, maybe a feature is broken, or there's an error that crashes part of the server. If you didn’t expect it, it’s probably a bug. -### 🐞 Bugs -Something doesn’t work as expected — maybe a feature is broken, or there's an error that crashes part of the server. If you didn’t expect it, it’s probably a bug. - -### 🚨 Exploits +### Exploits These are security issues or unintended mechanics that can be abused by players. These are especially important to report in detail. -### 📌 Others +### Others This can include performance issues, weird behaviors, missing features that *should* be there, or anything else you think is worth mentioning. ---- - -## 📝 How to Report an Issue +## 8.1.2 How to Report an Issue Please open an issue here on GitHub and include the following: -### 🔧 Target +### Target Tell us where the issue happens: - `Main Server` - `Cast Server` - `Auth Server` - `Common` (shared code or logic used by multiple servers) -### 📁 File Path +### File Path Point to the exact file and line (if possible). For example: `src/AuthServer/LoginHandler.cpp` - -### ❓ What’s the Issue? +### What’s the Issue? Describe clearly what’s going wrong. Include: - What you expected to happen - What actually happened - Any logs, screenshots, or relevant outputs -### 🔁 How to Reproduce +### How to Reproduce Explain step-by-step how we can trigger the problem. The simpler, the better. Example: 1. Connect to the Auth Server with empty credentials 2. Server crashes with `NullPointerException` ---- +### Optional: Operating System +The emulator may have issues only on Linux, or perhaps only on Windows. If that's the case, let us know. + -## ✅ Found a Fix? +## Found a Fix? Feel free to open a PR! -If you’ve already found a fix — awesome! -In that case, **please open a Pull Request (PR)** along with your issue so others can review and merge it. +If you’ve already found a fix that's even better! +In that case, **please open a Pull Request (PR)** along with your issue so we can review and merge it. Be sure your PR references the issue number (e.g. `Fixes #42`) so everything stays linked together. ---- -Thanks again for helping improve the project! 🚀 +### Thanks again for helping improve the project! ## Next diff --git a/doc/requirements_installation.md b/doc/requirements_installation.md index befe6f38..af2411a8 100644 --- a/doc/requirements_installation.md +++ b/doc/requirements_installation.md @@ -1,39 +1,34 @@ # 3.1 Requirements & Installation -## Requirements -At the moment, this emulator runs only on Windows, and it requires either the MSVC or Clang compiler. That’s because a few compiler-specific pragmas were used along the way. - -Support for Linux and GCC is definitely on my radar, but that’s something planned for a future release — so stay tuned. - -You will also need >= Python 3.10. - -## Installation -The installation process is pretty much the same as it was in version 1.0 when it comes to setting up the servers. Most of the dependencies are handled through vcpkg, and a vcpkg.json file is already provided to help with that. - -Here’s how to get everything set up: -1. Clone this repository locally. -2. Open a terminal and cd into your project’s ExternalLibraries folder: `cd /ExternalLibraries` -3. Clone vcpkg into this directory: `git clone https://github.com/microsoft/vcpkg.git` -4. Go into the new vcpkg folder and bootstrap it: `cd vcpkg` followed by `.\bootstrap-vcpkg.bat` -5. Integrate vcpkg with Visual Studio: `.\vcpkg integrate install` -6. Move the provided vcpkg.json (in the root directory) file into the newly created vcpkg folder. -7. Run the install: `.\vcpkg install` -8. Open the visual studio solution (.sln in the root directory) -9. For each project (MainServer, CastServer, AuthServer, Common): - - Go to Project Properties > Configuration Properties > vcpkg - - Under Installed Directory, set it to your vcpkg install path. Example: `..\ExternalLibraries\vcpkg\vcpkg_installed` - - ⚠️ Important: Only Release mode is fully set up with the right paths. If you want to compile in Debug mode, you’ll need to manually copy over the paths from Release -10. Once everything is configured, build the projects in this order: Common first, and then the servers right after (MainServer / AuthServer / CastServer). - -⚠️ Note: if your main server fails to compile with an error `python (...) generate_command_includes.py exited with code 1`: -- right click on the main server => properties -- Go to "Build Events" => "Pre-Build Events" -- change `python "$(ProjectDir)generate_command_includes.py"` to just `"$(ProjectDir)generate_command_includes.py"` -- If this fix does not work for you, please open up a new Issue. - -The resulting .exe files will be placed in your project's x64 output folder. - +## 3.1.1 Requirements +- Python >= 3.10 +- CMake >= 3.26 +- GCC >= 13 or MSVC >= 14.10 (Clang: untested) +- C++23 standard or newer + +## 3.1.2 Installation +### 3.1.2.1 Automatic Linux Setup +If you want an easy setup for Linux (based on Docker), then [this repository](https://github.com/ToyBattles/MicroVolts-Server-Docker) is the best tool for you. Follow the steps shown on it and you'll be ready in only a few minutes. + + +### 3.1.2.2 Manual setup (Windows) +1) Clone this repository then go to its folder: `cd ` - make sure you are inside the MicrovoltsEmulator folder (root of this repository) +2) Clone vcpkg inside ExternalLibraries: `git clone https://github.com/microsoft/vcpkg.git ExternalLibraries\vcpkg` +3) Bootstrap it: `.\ExternalLibraries\vcpkg\bootstrap-vcpkg.bat` +4) Generate build files: `cmake -B build -S . -A x64 -DCMAKE_TOOLCHAIN_FILE=ExternalLibraries/vcpkg/scripts/buildsystems/vcpkg.cmake` +5) Build the project: `cmake --build build --config Release` +6) Output (exes) inside Release folder + +### 3.1.2.3 Manual setup (Linux) +1) Clone this repository then go to its folder: `cd Temporarily removed) & Core stuff - [@TipicoDev](https://github.com/TipicoDev) - some Core stuff, CGD archive tools - [@sw1ndle777](https://github.com/sw1ndle777) - general help & exploit fixes diff --git a/doc/tool_for_setup.md b/doc/tool_for_setup.md new file mode 100644 index 00000000..4a60c074 --- /dev/null +++ b/doc/tool_for_setup.md @@ -0,0 +1,41 @@ +## 🛠️ Setup Tool (GUI) + +A GUI tool that makes setting up your own local MicroVolts server quick and easy. + +> 💡 Created by [@mikael](https://github.com/mikael) + +--- + +### 🔍 Preview + +
+ +
+ +
+ +
+ +--- + +### ✅ Prerequisites + +- Git https://git-scm.com/downloads/win +- Visual Studio https://visualstudio.microsoft.com/downloads/ +- Python https://www.python.org/downloads/ +--- + +You can watch a simple video tutorial [here](https://www.youtube.com/watch?v=nZYgwMuQXvo). + +### 🧩 Having Issues? + +If you encounter problems: + +- Open an issue directly on the public repository for the tool [here](https://github.com/Mikael/MicroVolts-Server-Setup) +- Or open an issue in this repository — we’ll make sure it gets forwarded + +--- + +### 📦 Download + +➡️ [**Download the Setup Tool (MVTool)**](https://github.com/Mikael/MicroVolts-Server-Setup) diff --git a/doc/updater_overview.md b/doc/updater_overview.md index 5cf7cd6c..5890a3d2 100644 --- a/doc/updater_overview.md +++ b/doc/updater_overview.md @@ -1,6 +1,6 @@ # 7.1 The Updater: How the launcher retrieves updates -## Update System Overview +## 7.1.1 Update System Overview Starting with version 2.0, the Microvolts updater tool is available for creating client update packages. This document explains: @@ -16,6 +16,7 @@ The system supports two approaches: - **Partial updates** (not supported by our tool) ## Configuration Files +The following files can be found in the root directory of the game folder. ### `updateinfo.ini` Specifies download locations: ```ini @@ -44,7 +45,7 @@ exe = bin/Microvolts.exe - The game executable path is always last -## Creating Update Packages +## 7.1.2 Creating Update Packages ### Required folder structure Your update server (or website, or whenever you put all your update packages) needs this exact structure: ```cpp @@ -88,7 +89,7 @@ Example: ``` -## Using the updater +## 7.1.3 Using the updater Everything mentioned above is almost automatically done through the `updater.py` utility, that you can find inside `Client/updater.py`. You will need to install all the necessary packages for Python. diff --git a/doc/whats_next.md b/doc/whats_next.md index fb9d1630..6c101c20 100644 --- a/doc/whats_next.md +++ b/doc/whats_next.md @@ -1,21 +1,21 @@ -# 🚀 What’s Next? +# What’s Next? -It’s been quite a journey — over 3 years since I first started working on this server emulator. What began as a small experiment has grown into something much bigger, and I’m proud of how far it’s come. +It’s been quite a journey: over 3 years since we first started working on this server emulator. What began as a small experiment has grown into something much bigger, and I’m proud of how far it’s come. -Most of the core features from the original servers are now implemented. The base is solid. Sure, there’s always room for improvements (there always will be), but I finally feel that the emulator is in a place where it *works* — and works well. +Most of the core features from the original servers are now implemented. The base is solid. Sure, there’s always room for improvements (there always will be), but we finally feel that the emulator is in a place where it *works*, and works well. -That said, this project was never meant to be built by just one person forever. No one can think of everything or implement every idea perfectly. That’s exactly why I made it open source. I want this emulator to be community-driven — something that grows and improves thanks to all of you. +That said, this project was never meant to be built by a super small team forever. No one can think of everything or implement every idea perfectly. That’s exactly why we made it open source. We want this emulator to be community-driven, something that grows and improves thanks to all of you. -### 🙌 How You Can Help +### How YOU Can Help - **Found a bug?** Report it. - **Know how to fix it?** Open a PR. - **Have a cool idea or feature?** Let’s talk about it. - **Want to build something on top of this?** Go for it! Even entirely new forks or versions with different goals are more than welcome, as long as they push things forward. -The emulator is usable, but it's far from "done." There's no roadmap set in stone — and that’s the beauty of it. What comes next depends on all of us. +The emulator is usable, but it's far from "done." There's no roadmap set in stone and that’s the beauty of it. What comes next depends on all of us. Thanks for being part of this. Let’s keep it going. -— SoWeBegin +— ToyBattles HQ diff --git a/microvolts-db.sql b/microvolts-db.sql index 6f719900..231ebb6c 100644 --- a/microvolts-db.sql +++ b/microvolts-db.sql @@ -494,7 +494,7 @@ CREATE TABLE IF NOT EXISTS `users` ( -- Dump dei dati della tabella microvolts-db.users: ~1 rows (circa) INSERT INTO `users` (`AccountID`, `AccountKey`, `Username`, `Password`, `Nickname`, `Grade`, `SuspendedUntil`, `SuspensionReason`, `MutedUntil`, `MuteReason`, `MutedBy`, `Level`, `Experience`, `Kills`, `Deaths`, `Assists`, `Wins`, `Loses`, `Draws`, `ClanContribution`, `ClanWins`, `ClanLoses`, `ClanDraws`, `ClanName`, `ClanKills`, `ClanDeaths`, `ClanAssists`, `ClanIcon1`, `ClanIcon2`, `ClanID`, `MeleeKills`, `RifleKills`, `ShotgunKills`, `SniperKills`, `GatlingKills`, `BazookaKills`, `GrenadeKills`, `Headshots`, `HighestKillstreak`, `Playtime`, `ZombieKills`, `InfectedKills`, `MicroPoints`, `RockTotens`, `coins`, `Battery`, `MaxBattery`, `MaxInventory`, `HasFinishedTutorial`, `SingleWaveAttempts`, `LastCharacterUsed`, `LuckyPoints`, `HighestSinglewaveScore`, `HighestSinglewaveStage`, `VipExperience`, `LatestWeeklyRewardDay`, `LatestMonthlyRewardDay`, `Secret`, `SecretCreatedAt`, `BackupCodes`, `LoginOK`, `ClanMemberType`, `Email`, `isEmailVerified`, `Coupons`, `LastLogged`, `RoomCreationDisabledUntil`, `LastIP`, `VerificationToken`, `Verified`, `Has2FA`, `CommentBanned`, `original_secret`, `original_password`, `secret_expiry`, `DiscordID`, `LastLocalIp`, `HWID`) VALUES - (680155, 2957703414, 'test', '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', 'test', 1, '', NULL, '0', NULL, '0', 10, 35000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99999300, NULL, 5000, 5000, 200, 1, 100, 0, 60, 0, 0, 0, '2025-06-11', '2025-06-11', '', NULL, NULL, 0, 'TeamR', NULL, 0, 0, '2025-06-11 00:05:36', NULL, '127.0.0.1', NULL, NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, ''); + (680155, NULL, 'test', '9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08', 'test', 1, '', NULL, '0', NULL, '0', 10, 35000, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NULL, 0, 0, 0, 0, 0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 99999300, NULL, 5000, 5000, 200, 1, 100, 0, 60, 0, 0, 0, '2025-06-11', '2025-06-11', '', NULL, NULL, 0, 'TeamR', NULL, 0, 0, '2025-06-11 00:05:36', NULL, '127.0.0.1', NULL, NULL, 0, 0, NULL, NULL, NULL, NULL, NULL, ''); -- Dump della struttura di tabella microvolts-db.user_ip_history CREATE TABLE IF NOT EXISTS `user_ip_history` ( diff --git a/vcpkg.json b/vcpkg.json index 4385ccd6..fc3a3f9b 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,145 +1,49 @@ + { "name": "microvolts-emulator", "version": "1.0.0", "builtin-baseline": "4e08971f3ddc13018ca858a692efe92d3b6b9fce", "dependencies": [ - { - "name": "openssl", - "version>=": "3.0.2" - }, - { - "name": "jwt-cpp", - "version>=": "0.7.0" - }, - { - "name": "boost-json", - "version>=": "1.83.0" - }, - { - "name": "boost-beast", - "version>=": "1.86.0" - }, - { - "name": "mariadb-connector-cpp", - "version>=": "1.1.4" - }, - { - "name": "boost-assert", - "version>=": "1.82.0#2" - }, + "openssl", + "jwt-cpp", + "boost-json", + "boost-beast", + "boost-system", + "boost-asio", + "mariadb-connector-cpp", { "name": "cryptopp", - "version>=": "8.2.0-2#0", "features": [ "pem-pack" ] }, - { - "name": "boost-build", - "version>=": "1.82.0#2" - }, - { - "name": "boost-config", - "version>=": "1.82.0#2" - }, - { - "name": "boost-container-hash", - "version>=": "1.82.0#2" - }, - { - "name": "boost-container", - "version>=": "1.82.0#2" - }, - { - "name": "boost-core", - "version>=": "1.82.0#2" - }, - { - "name": "boost-describe", - "version>=": "1.82.0#2" - }, - { - "name": "boost-interprocess", - "version>=": "1.82.0#2" - }, - { - "name": "boost-intrusive", - "version>=": "1.82.0#2" - }, - { - "name": "boost-move", - "version>=": "1.82.0#2" - }, - { - "name": "boost-mp11", - "version>=": "1.82.0#2" - }, - { - "name": "boost-predef", - "version>=": "1.82.0#2" - }, - { - "name": "asio", - "version>=": "1.24.0" - }, - { - "name": "boost-preprocessor", - "version>=": "1.82.0#2" - }, - { - "name": "boost-static-assert", - "version>=": "1.82.0#2" - }, - { - "name": "boost-throw-exception", - "version>=": "1.82.0#2" - }, - { - "name": "boost-tuple", - "version>=": "1.82.0#2" - }, - { - "name": "boost-type-traits", - "version>=": "1.82.0#2" - }, - { - "name": "boost-uninstall", - "version>=": "1.82.0#2" - }, - { - "name": "boost-unordered", - "version>=": "1.82.0#2" - }, - { - "name": "boost-vcpkg-helpers", - "version>=": "1.82.0#2" - }, - { - "name": "boost-winapi", - "version>=": "1.82.0#2" - }, - { - "name": "range-v3", - "version>=": "0.12.0#1" - }, - { - "name": "sqlite3", - "version>=": "3.42.0#1" - }, - { - "name": "sqlitecpp", - "version>=": "3.3.0" - }, - { - "name": "vcpkg-cmake-config", - "version>=": "2022-02-06#1" - }, - { - "name": "vcpkg-cmake-get-vars", - "version>=": "2023-03-02" - }, - { - "name": "vcpkg-cmake", - "version>=": "2023-05-04" - } + "boost-assert", + "boost-build", + "boost-config", + "boost-container-hash", + "boost-container", + "boost-core", + "boost-describe", + "boost-interprocess", + "boost-intrusive", + "boost-move", + "boost-mp11", + "boost-predef", + "asio", + "boost-preprocessor", + "boost-static-assert", + "boost-throw-exception", + "boost-tuple", + "boost-type-traits", + "boost-uninstall", + "boost-unordered", + "boost-vcpkg-helpers", + "boost-winapi", + "range-v3", + "sqlite3", + "sqlitecpp", + "vcpkg-cmake-config", + "vcpkg-cmake-get-vars", + "vcpkg-cmake", + "directxmath" ], "overrides": [ {