diff --git a/dAuthServer/AuthServer.cpp b/dAuthServer/AuthServer.cpp index 7cbe390a7..786d9d829 100644 --- a/dAuthServer/AuthServer.cpp +++ b/dAuthServer/AuthServer.cpp @@ -21,6 +21,8 @@ //Auth includes: #include "AuthPackets.h" +#include "BitStreamUtils.h" +#include "CommonPackets.h" #include "ServiceType.h" #include "MessageType/Server.h" #include "MessageType/Auth.h" @@ -172,16 +174,20 @@ int main(int argc, char** argv) { void HandlePacket(Packet* packet) { if (packet->length < 4) return; + if (packet->data[0] != ID_USER_PACKET_ENUM) return; - if (packet->data[0] == ID_USER_PACKET_ENUM) { - if (static_cast(packet->data[1]) == ServiceType::COMMON) { - if (static_cast(packet->data[3]) == MessageType::Server::VERSION_CONFIRM) { - AuthPackets::HandleHandshake(Game::server, packet); - } - } else if (static_cast(packet->data[1]) == ServiceType::AUTH) { - if (static_cast(packet->data[3]) == MessageType::Auth::LOGIN_REQUEST) { - AuthPackets::HandleLoginRequest(Game::server, packet); - } - } + RakNet::BitStream inStream(packet->data, packet->length, false); + LUBitStream header; + if (!header.ReadHeader(inStream)) return; + + switch (header.connectionType) { + case ServiceType::COMMON: + CommonPackets::Handle(inStream, packet->systemAddress, header.internalPacketID); + break; + case ServiceType::AUTH: + AuthPackets::Handle(inStream, packet->systemAddress, header.internalPacketID); + break; + default: + break; } } diff --git a/dNet/AuthPackets.cpp b/dNet/AuthPackets.cpp index 0c130a147..ee94bbd0e 100644 --- a/dNet/AuthPackets.cpp +++ b/dNet/AuthPackets.cpp @@ -26,15 +26,14 @@ #include "MessageType/Master.h" #include "eGameMasterLevel.h" #include "StringifiedEnum.h" + +#include +#include +#include namespace { std::vector claimCodes; } -void Stamp::Serialize(RakNet::BitStream& outBitStream){ - outBitStream.Write(type); - outBitStream.Write(value); - outBitStream.Write(timestamp); -}; void AuthPackets::LoadClaimCodes() { if(!claimCodes.empty()) return; @@ -47,46 +46,6 @@ void AuthPackets::LoadClaimCodes() { } } -void AuthPackets::HandleHandshake(dServer* server, Packet* packet) { - CINSTREAM_SKIP_HEADER - uint32_t clientVersion = 0; - inStream.Read(clientVersion); - inStream.IgnoreBytes(4); - - ServiceType serviceType; - inStream.Read(serviceType); - if (serviceType != ServiceType::CLIENT) LOG("WARNING: Service is not a Client!"); - inStream.IgnoreBytes(2); - - uint32_t processID; - inStream.Read(processID); - - uint16_t port; - inStream.Read(port); - if (port != packet->systemAddress.port) LOG("WARNING: Port written in packet does not match the port the client is connecting over!"); - - inStream.IgnoreBytes(33); - - LOG_DEBUG("Client Data [Version: %i, Service: %s, Process: %u, Port: %u, Sysaddr Port: %u]", clientVersion, StringifiedEnum::ToString(serviceType).data(), processID, port, packet->systemAddress.port); - - SendHandshake(server, packet->systemAddress, server->GetIP(), server->GetPort(), server->GetServerType()); -} - -void AuthPackets::SendHandshake(dServer* server, const SystemAddress& sysAddr, const std::string& nextServerIP, uint16_t nextServerPort, const ServiceType serverType) { - RakNet::BitStream bitStream; - BitStreamUtils::WriteHeader(bitStream, ServiceType::COMMON, MessageType::Server::VERSION_CONFIRM); - - const auto& clientNetVersionString = Game::config->GetValue("client_net_version"); - const uint32_t clientNetVersion = GeneralUtils::TryParse(clientNetVersionString).value_or(171022); - - bitStream.Write(clientNetVersion); - bitStream.Write(861228100); - bitStream.Write(static_cast(serverType)); - bitStream.Write(219818307120); - - server->Send(bitStream, sysAddr, false); -} - std::string CleanReceivedString(const std::string& str) { std::string toReturn = str; const auto removed = std::ranges::find_if(toReturn, [](unsigned char c) { return isprint(c) == 0 && isblank(c) == 0; }); @@ -94,58 +53,88 @@ std::string CleanReceivedString(const std::string& str) { return toReturn; } -void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { - CINSTREAM_SKIP_HEADER; +void AuthPackets::LoginRequest::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(username); + bitStream.Write(password); + bitStream.Write(localeID); + bitStream.Write(clientOS); + bitStream.Write(memoryStats); + bitStream.Write(videoCard); + bitStream.Write(numberOfProcessors); + bitStream.Write(processorType); + bitStream.Write(processorLevel); + bitStream.Write(processorRevision); + bitStream.Write(osVersionInfoSize); + bitStream.Write(majorVersion); + bitStream.Write(minorVersion); + bitStream.Write(buildNumber); + bitStream.Write(platformID); +} + +bool AuthPackets::LoginRequest::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(username)); + VALIDATE_READ(bitStream.Read(password)); + VALIDATE_READ(bitStream.Read(localeID)); + VALIDATE_READ(bitStream.Read(clientOS)); + VALIDATE_READ(bitStream.Read(memoryStats)); + VALIDATE_READ(bitStream.Read(videoCard)); + VALIDATE_READ(bitStream.Read(numberOfProcessors)); + VALIDATE_READ(bitStream.Read(processorType)); + VALIDATE_READ(bitStream.Read(processorLevel)); + VALIDATE_READ(bitStream.Read(processorRevision)); + VALIDATE_READ(bitStream.Read(osVersionInfoSize)); + VALIDATE_READ(bitStream.Read(majorVersion)); + VALIDATE_READ(bitStream.Read(minorVersion)); + VALIDATE_READ(bitStream.Read(buildNumber)); + VALIDATE_READ(bitStream.Read(platformID)); + return true; +} + +namespace { + const std::map()>> g_Handlers = { + { MessageType::Auth::LOGIN_REQUEST, []() { return std::make_unique(); } }, + }; +} + +void AuthPackets::Handle(RakNet::BitStream& inStream, const SystemAddress& sysAddr, const uint32_t packetID) { + const auto messageID = static_cast(packetID); + const auto it = g_Handlers.find(messageID); + if (it == g_Handlers.end()) { + LOG_DEBUG("Unhandled auth packet %i", packetID); + return; + } + + auto request = it->second(); + request->sysAddr = sysAddr; + if (!request->Deserialize(inStream)) { + LOG("Failed to read auth packet %s", StringifiedEnum::ToString(messageID).data()); + return; + } + request->Handle(); +} + +void AuthPackets::LoginRequest::Handle() { + auto* const server = Game::server; + const auto& packet = *this; // the old handler's sysAddr std::vector stamps; stamps.emplace_back(eStamps::PASSPORT_AUTH_START, 0); - LUWString usernameLUString; - inStream.Read(usernameLUString); - const auto username = usernameLUString.GetAsString(); + const auto username = this->username.GetAsString(); - LUWString password(41); - inStream.Read(password); + LOG_DEBUG("Locale ID: %s", StringifiedEnum::ToString(localeID).data()); - LanguageCodeID locale_id; - inStream.Read(locale_id); - LOG_DEBUG("Locale ID: %s", StringifiedEnum::ToString(locale_id).data()); - - ClientOS clientOS; - inStream.Read(clientOS); LOG_DEBUG("Operating System: %s", StringifiedEnum::ToString(clientOS).data()); stamps.emplace_back(eStamps::PASSPORT_AUTH_CLIENT_OS, 0); - LUWString memoryStats(256); - inStream.Read(memoryStats); LOG_DEBUG("Memory Stats [%s]", CleanReceivedString(memoryStats.GetAsString()).c_str()); - LUWString videoCard(128); - inStream.Read(videoCard); LOG_DEBUG("VideoCard Info: [%s]", CleanReceivedString(videoCard.GetAsString()).c_str()); // Processor/CPU info - uint32_t numOfProcessors; - inStream.Read(numOfProcessors); - uint32_t processorType; - inStream.Read(processorType); - uint16_t processorLevel; - inStream.Read(processorLevel); - uint16_t processorRevision; - inStream.Read(processorRevision); - LOG_DEBUG("CPU Info: [#Processors: %i, Processor Type: %i, Processor Level: %i, Processor Revision: %i]", numOfProcessors, processorType, processorLevel, processorRevision); + LOG_DEBUG("CPU Info: [#Processors: %i, Processor Type: %i, Processor Level: %i, Processor Revision: %i]", numberOfProcessors, processorType, processorLevel, processorRevision); // OS Info - uint32_t osVersionInfoSize; - inStream.Read(osVersionInfoSize); - uint32_t majorVersion; - inStream.Read(majorVersion); - uint32_t minorVersion; - inStream.Read(minorVersion); - uint32_t buildNumber; - inStream.Read(buildNumber); - uint32_t platformID; - inStream.Read(platformID); LOG_DEBUG("OS Info: [Size: %i, Major: %i, Minor %i, Buid#: %i, platformID: %i]", osVersionInfoSize, majorVersion, minorVersion, buildNumber, platformID); // Fetch account details @@ -154,7 +143,7 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { if (!accountInfo) { LOG("No user by name %s found!", username.c_str()); stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::INVALID_USER, "", "", 2001, username, stamps); + AuthPackets::SendLoginResponse(server, sysAddr, eLoginResponse::INVALID_USER, "", "", 2001, username, stamps); return; } @@ -162,7 +151,7 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { // and a failed attempt changes nothing (an expired ban is only lifted for the real owner) if (::bcrypt_checkpw(password.GetAsString().c_str(), accountInfo->bcryptPassword.c_str()) != 0) { stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::WRONG_PASS, "", "", 2001, username, stamps); + AuthPackets::SendLoginResponse(server, sysAddr, eLoginResponse::WRONG_PASS, "", "", 2001, username, stamps); LOG("Wrong password used"); return; } @@ -170,7 +159,7 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { //If we aren't running in live mode, then only GMs are allowed to enter: if (Game::config->GetValue("closed_to_non_devs", false) && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) { stamps.emplace_back(eStamps::GM_REQUIRED, 1); - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "The server is currently only open to developers.", "", 2001, username, stamps); + AuthPackets::SendLoginResponse(server, sysAddr, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "The server is currently only open to developers.", "", 2001, username, stamps); return; } @@ -178,7 +167,7 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { //Check to see if we have a play key: if (accountInfo->playKeyId == 0) { stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a play key associated with it!", "", 2001, username, stamps); + AuthPackets::SendLoginResponse(server, sysAddr, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a play key associated with it!", "", 2001, username, stamps); LOG("User %s tried to log in, but they don't have a play key.", username.c_str()); return; } @@ -188,13 +177,13 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { if (!playKeyStatus) { stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a valid play key associated with it!", "", 2001, username, stamps); + AuthPackets::SendLoginResponse(server, sysAddr, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a valid play key associated with it!", "", 2001, username, stamps); return; } if (!playKeyStatus.value()) { stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your play key has been disabled.", "", 2001, username, stamps); + AuthPackets::SendLoginResponse(server, sysAddr, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your play key has been disabled.", "", 2001, username, stamps); LOG("User %s tried to log in, but their play key was disabled", username.c_str()); return; } @@ -220,18 +209,18 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { message = std::string("You are banned until ") + until + "."; } if (!accountInfo->banReason.empty()) message += (message.empty() ? "" : " ") + std::string("Reason: ") + accountInfo->banReason; - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::BANNED, message, "", 2001, username, stamps); + AuthPackets::SendLoginResponse(server, sysAddr, eLoginResponse::BANNED, message, "", 2001, username, stamps); return; } if (accountInfo->locked) { stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); - AuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::ACCOUNT_LOCKED, "", "", 2001, username, stamps); + AuthPackets::SendLoginResponse(server, sysAddr, eLoginResponse::ACCOUNT_LOCKED, "", "", 2001, username, stamps); return; } { - SystemAddress system = packet->systemAddress; //Copy the sysAddr before the Packet gets destroyed from main + SystemAddress system = sysAddr; //Copy the sysAddr before the Packet gets destroyed from main // Where accounts log in from, so staff can see accounts that share a connection (log_login_addresses, on by default) if (Game::config->GetValue("log_login_addresses") != "0") { @@ -256,74 +245,45 @@ void AuthPackets::HandleLoginRequest(dServer* server, Packet* packet) { void AuthPackets::SendLoginResponse(dServer* server, const SystemAddress& sysAddr, eLoginResponse responseCode, const std::string& errorMsg, const std::string& wServerIP, uint16_t wServerPort, std::string username, std::vector& stamps) { stamps.emplace_back(eStamps::PASSPORT_AUTH_IM_LOGIN_START, 1); - RakNet::BitStream loginResponse; - BitStreamUtils::WriteHeader(loginResponse, ServiceType::CLIENT, MessageType::Client::LOGIN_RESPONSE); + ClientPackets::LoginResponse loginResponse; - loginResponse.Write(responseCode); + loginResponse.responseCode = responseCode; // Event Gating - loginResponse.Write(LUString(Game::config->GetValue("event_1"))); - loginResponse.Write(LUString(Game::config->GetValue("event_2"))); - loginResponse.Write(LUString(Game::config->GetValue("event_3"))); - loginResponse.Write(LUString(Game::config->GetValue("event_4"))); - loginResponse.Write(LUString(Game::config->GetValue("event_5"))); - loginResponse.Write(LUString(Game::config->GetValue("event_6"))); - loginResponse.Write(LUString(Game::config->GetValue("event_7"))); - loginResponse.Write(LUString(Game::config->GetValue("event_8"))); + loginResponse.events[0] = LUString(Game::config->GetValue("event_1")); + loginResponse.events[1] = LUString(Game::config->GetValue("event_2")); + loginResponse.events[2] = LUString(Game::config->GetValue("event_3")); + loginResponse.events[3] = LUString(Game::config->GetValue("event_4")); + loginResponse.events[4] = LUString(Game::config->GetValue("event_5")); + loginResponse.events[5] = LUString(Game::config->GetValue("event_6")); + loginResponse.events[6] = LUString(Game::config->GetValue("event_7")); + loginResponse.events[7] = LUString(Game::config->GetValue("event_8")); - const uint16_t version_major = + loginResponse.versionMajor = GeneralUtils::TryParse(Game::config->GetValue("version_major")).value_or(ClientVersion::major); - const uint16_t version_current = + loginResponse.versionCurrent = GeneralUtils::TryParse(Game::config->GetValue("version_current")).value_or(ClientVersion::current); - const uint16_t version_minor = + loginResponse.versionMinor = GeneralUtils::TryParse(Game::config->GetValue("version_minor")).value_or(ClientVersion::minor); - loginResponse.Write(version_major); - loginResponse.Write(version_current); - loginResponse.Write(version_minor); - - // Writes the user key + // The user key uint32_t sessionKey = GeneralUtils::GenerateRandomNumber(); std::string userHash = std::to_string(sessionKey); userHash = md5(userHash); - loginResponse.Write(LUWString(userHash)); + loginResponse.userKey = LUWString(userHash); // World Server IP - loginResponse.Write(LUString(wServerIP)); - // Chat Server IP (unused) - loginResponse.Write(LUString("")); - + loginResponse.worldServerIP = LUString(wServerIP); // World Server Redirect port - loginResponse.Write(wServerPort); - // Char Server Redirect port (unused) - loginResponse.Write(static_cast(0)); + loginResponse.worldServerPort = wServerPort; - // CDN Key - loginResponse.Write(LUString("")); - - // CDN Ticket - loginResponse.Write(LUString("00000000-0000-0000-0000-000000000000", 37)); - - // Language - loginResponse.Write(Language::en_US); - - // Write the localization - loginResponse.Write(LUString("US", 3)); - - loginResponse.Write(false); // Just upgraded from F2P - loginResponse.Write(false); // User is F2P - loginResponse.Write(0); // Time Remaining in F2P - - // Write custom error message - loginResponse.Write(errorMsg.length()); - loginResponse.Write(LUWString(errorMsg, static_cast(errorMsg.length()))); + // Custom error message + loginResponse.errorMessage = errorMsg; stamps.emplace_back(eStamps::PASSPORT_AUTH_WORLD_COMMUNICATION_FINISH, 1); + loginResponse.stamps = stamps; - loginResponse.Write((sizeof(Stamp) * stamps.size()) + sizeof(uint32_t)); - for (auto& stamp : stamps) stamp.Serialize(loginResponse); - - server->Send(loginResponse, sysAddr, false); + loginResponse.Send(sysAddr); //Inform the master server that we've created a session for this user: if (responseCode == eLoginResponse::SUCCESS) { CBITSTREAM; diff --git a/dNet/AuthPackets.h b/dNet/AuthPackets.h index 156862eb5..a3b86911f 100644 --- a/dNet/AuthPackets.h +++ b/dNet/AuthPackets.h @@ -5,67 +5,14 @@ #include "dCommonVars.h" #include "dNetCommon.h" #include "magic_enum.hpp" +#include "BitStreamUtils.h" +#include "ClientPackets.h" +#include "MessageType/Auth.h" enum class eLoginResponse : uint8_t; enum class ServiceType : uint16_t; class dServer; -enum class eStamps : uint32_t { - PASSPORT_AUTH_START, - PASSPORT_AUTH_BYPASS, - PASSPORT_AUTH_ERROR, - PASSPORT_AUTH_DB_SELECT_START, - PASSPORT_AUTH_DB_SELECT_FINISH, - PASSPORT_AUTH_DB_INSERT_START, - PASSPORT_AUTH_DB_INSERT_FINISH, - PASSPORT_AUTH_LEGOINT_COMMUNICATION_START, - PASSPORT_AUTH_LEGOINT_RECEIVED, - PASSPORT_AUTH_LEGOINT_THREAD_SPAWN, - PASSPORT_AUTH_LEGOINT_WEBSERVICE_START, - PASSPORT_AUTH_LEGOINT_WEBSERVICE_FINISH, - PASSPORT_AUTH_LEGOINT_LEGOCLUB_START, - PASSPORT_AUTH_LEGOINT_LEGOCLUB_FINISH, - PASSPORT_AUTH_LEGOINT_THREAD_FINISH, - PASSPORT_AUTH_LEGOINT_REPLY, - PASSPORT_AUTH_LEGOINT_ERROR, - PASSPORT_AUTH_LEGOINT_COMMUNICATION_END, - PASSPORT_AUTH_LEGOINT_DISCONNECT, - PASSPORT_AUTH_WORLD_COMMUNICATION_START, - PASSPORT_AUTH_CLIENT_OS, - PASSPORT_AUTH_WORLD_PACKET_RECEIVED, - PASSPORT_AUTH_IM_COMMUNICATION_START, - PASSPORT_AUTH_IM_LOGIN_START, - PASSPORT_AUTH_IM_LOGIN_ALREADY_LOGGED_IN, - PASSPORT_AUTH_IM_OTHER_LOGIN_REMOVED, - PASSPORT_AUTH_IM_LOGIN_QUEUED, - PASSPORT_AUTH_IM_LOGIN_RESPONSE, - PASSPORT_AUTH_IM_COMMUNICATION_END, - PASSPORT_AUTH_WORLD_SESSION_CONFIRM_TO_AUTH, - PASSPORT_AUTH_WORLD_COMMUNICATION_FINISH, - PASSPORT_AUTH_WORLD_DISCONNECT, - NO_LEGO_INTERFACE, - DB_ERROR, - GM_REQUIRED, - NO_LEGO_WEBSERVICE_XML, - LEGO_WEBSERVICE_TIMEOUT, - LEGO_WEBSERVICE_ERROR, - NO_WORLD_SERVER -}; - -struct Stamp { - eStamps type; - uint32_t value; - uint64_t timestamp; - - Stamp(eStamps type, uint32_t value, uint64_t timestamp = time(nullptr)){ - this->type = type; - this->value = value; - this->timestamp = timestamp; - } - - void Serialize(RakNet::BitStream& outBitStream); -}; - enum class ClientOS : uint8_t { UNKNOWN, WINDOWS, @@ -84,21 +31,44 @@ struct magic_enum::customize::enum_range { static constexpr int max = 2057; }; -enum class Language : uint32_t { - en_US, - pl_US, - de_DE, - en_GB, -}; namespace AuthPackets { - void HandleHandshake(dServer* server, Packet* packet); - void SendHandshake(dServer* server, const SystemAddress& sysAddr, const std::string& nextServerIP, uint16_t nextServerPort, const ServiceType serverType); + // Client -> auth server. The username and password, plus a description of the client's machine. + struct LoginRequest : public LUBitStream { + // Set by the dispatcher before Deserialize and Handle. + SystemAddress sysAddr = UNASSIGNED_SYSTEM_ADDRESS; - void HandleLoginRequest(dServer* server, Packet* packet); + LUWString username{ 33 }; + LUWString password{ 41 }; + LanguageCodeID localeID{}; + ClientOS clientOS{}; + LUWString memoryStats{ 256 }; + LUWString videoCard{ 128 }; + // Processor + uint32_t numberOfProcessors{}; + uint32_t processorType{}; + uint16_t processorLevel{}; + uint16_t processorRevision{}; + // OS version + uint32_t osVersionInfoSize{}; + uint32_t majorVersion{}; + uint32_t minorVersion{}; + uint32_t buildNumber{}; + uint32_t platformID{}; + + LoginRequest() : LUBitStream(ServiceType::AUTH, MessageType::Auth::LOGIN_REQUEST) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + void Handle() override; + }; + + // Handles a ServiceType::AUTH packet whose header has already been read from inStream. + void Handle(RakNet::BitStream& inStream, const SystemAddress& sysAddr, uint32_t packetID); + + // Answers a login with a ClientPackets::LoginResponse filled from the server's settings (event gating, client + // version) and a new session key; on success also registers that session key with the master server. void SendLoginResponse(dServer* server, const SystemAddress& sysAddr, eLoginResponse responseCode, const std::string& errorMsg, const std::string& wServerIP, uint16_t wServerPort, std::string username, std::vector& stamps); void LoadClaimCodes(); - } #endif // AUTHPACKETS_H diff --git a/dNet/CMakeLists.txt b/dNet/CMakeLists.txt index 9b780b84f..f2df367f1 100644 --- a/dNet/CMakeLists.txt +++ b/dNet/CMakeLists.txt @@ -1,4 +1,5 @@ set(DNET_SOURCES "AuthPackets.cpp" + "CommonPackets.cpp" "BitStreamUtils.cpp" "ChatPackets.cpp" "ClientPackets.cpp" diff --git a/dNet/ClientPackets.cpp b/dNet/ClientPackets.cpp index 43d36e0f2..9c269f9ee 100644 --- a/dNet/ClientPackets.cpp +++ b/dNet/ClientPackets.cpp @@ -6,6 +6,84 @@ #include "ClientPackets.h" #include "dCommonVars.h" #include "PositionUpdate.h" +#include "eLoginResponse.h" + +static_assert(sizeof(Stamp) == 16, "the login response's stamp size field has always been 16 bytes per stamp"); + +void Stamp::Serialize(RakNet::BitStream& outBitStream) const { + outBitStream.Write(type); + outBitStream.Write(value); + outBitStream.Write(timestamp); +} + +bool Stamp::Deserialize(RakNet::BitStream& inBitStream) { + VALIDATE_READ(inBitStream.Read(type)); + VALIDATE_READ(inBitStream.Read(value)); + VALIDATE_READ(inBitStream.Read(timestamp)); + return true; +} + +namespace ClientPackets { + void LoginResponse::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(responseCode); + for (const auto& event : events) bitStream.Write(event); + bitStream.Write(versionMajor); + bitStream.Write(versionCurrent); + bitStream.Write(versionMinor); + bitStream.Write(userKey); + bitStream.Write(worldServerIP); + bitStream.Write(chatServerIP); + bitStream.Write(worldServerPort); + bitStream.Write(chatServerPort); + bitStream.Write(cdnKey); + bitStream.Write(cdnTicket); + bitStream.Write(language); + bitStream.Write(localization); + bitStream.Write(justUpgradedFromF2P); + bitStream.Write(isFreeToPlay); + bitStream.Write(freeToPlayTimeRemaining); + bitStream.Write(errorMessage.length()); + bitStream.Write(LUWString(errorMessage, static_cast(errorMessage.length()))); + bitStream.Write((sizeof(Stamp) * stamps.size()) + sizeof(uint32_t)); + for (const auto& stamp : stamps) stamp.Serialize(bitStream); + } + + bool LoginResponse::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(responseCode)); + for (auto& event : events) VALIDATE_READ(bitStream.Read(event)); + VALIDATE_READ(bitStream.Read(versionMajor)); + VALIDATE_READ(bitStream.Read(versionCurrent)); + VALIDATE_READ(bitStream.Read(versionMinor)); + VALIDATE_READ(bitStream.Read(userKey)); + VALIDATE_READ(bitStream.Read(worldServerIP)); + VALIDATE_READ(bitStream.Read(chatServerIP)); + VALIDATE_READ(bitStream.Read(worldServerPort)); + VALIDATE_READ(bitStream.Read(chatServerPort)); + VALIDATE_READ(bitStream.Read(cdnKey)); + VALIDATE_READ(bitStream.Read(cdnTicket)); + VALIDATE_READ(bitStream.Read(language)); + VALIDATE_READ(bitStream.Read(localization)); + uint8_t flag{}; + VALIDATE_READ(bitStream.Read(flag)); + justUpgradedFromF2P = flag != 0; + VALIDATE_READ(bitStream.Read(flag)); + isFreeToPlay = flag != 0; + VALIDATE_READ(bitStream.Read(freeToPlayTimeRemaining)); + uint16_t errorLength{}; + VALIDATE_READ(bitStream.Read(errorLength)); + LUWString error(errorLength); + if (errorLength > 0) VALIDATE_READ(bitStream.Read(error)); // RakNet fails reads of 0 bits + errorMessage = error.GetAsString(); + uint32_t stampsSize{}; + VALIDATE_READ(bitStream.Read(stampsSize)); + if (stampsSize < sizeof(uint32_t) || (stampsSize - sizeof(uint32_t)) % sizeof(Stamp) != 0) return false; + const uint32_t stampCount = (stampsSize - sizeof(uint32_t)) / sizeof(Stamp); + if (stampCount > BITS_TO_BYTES(bitStream.GetNumberOfUnreadBits()) / sizeof(Stamp)) return false; + stamps.resize(stampCount); + for (auto& stamp : stamps) VALIDATE_READ(stamp.Deserialize(bitStream)); + return true; + } +} ChatMessage ClientPackets::HandleChatMessage(Packet* packet) { CINSTREAM_SKIP_HEADER; diff --git a/dNet/ClientPackets.h b/dNet/ClientPackets.h index a7d2941b5..2b5ed0184 100644 --- a/dNet/ClientPackets.h +++ b/dNet/ClientPackets.h @@ -7,12 +7,85 @@ #define CLIENTPACKETS_H #include +#include #include +#include + +#include "BitStreamUtils.h" +#include "MessageType/Client.h" + +enum class eLoginResponse : uint8_t; class PositionUpdate; struct Packet; +enum class eStamps : uint32_t { + PASSPORT_AUTH_START, + PASSPORT_AUTH_BYPASS, + PASSPORT_AUTH_ERROR, + PASSPORT_AUTH_DB_SELECT_START, + PASSPORT_AUTH_DB_SELECT_FINISH, + PASSPORT_AUTH_DB_INSERT_START, + PASSPORT_AUTH_DB_INSERT_FINISH, + PASSPORT_AUTH_LEGOINT_COMMUNICATION_START, + PASSPORT_AUTH_LEGOINT_RECEIVED, + PASSPORT_AUTH_LEGOINT_THREAD_SPAWN, + PASSPORT_AUTH_LEGOINT_WEBSERVICE_START, + PASSPORT_AUTH_LEGOINT_WEBSERVICE_FINISH, + PASSPORT_AUTH_LEGOINT_LEGOCLUB_START, + PASSPORT_AUTH_LEGOINT_LEGOCLUB_FINISH, + PASSPORT_AUTH_LEGOINT_THREAD_FINISH, + PASSPORT_AUTH_LEGOINT_REPLY, + PASSPORT_AUTH_LEGOINT_ERROR, + PASSPORT_AUTH_LEGOINT_COMMUNICATION_END, + PASSPORT_AUTH_LEGOINT_DISCONNECT, + PASSPORT_AUTH_WORLD_COMMUNICATION_START, + PASSPORT_AUTH_CLIENT_OS, + PASSPORT_AUTH_WORLD_PACKET_RECEIVED, + PASSPORT_AUTH_IM_COMMUNICATION_START, + PASSPORT_AUTH_IM_LOGIN_START, + PASSPORT_AUTH_IM_LOGIN_ALREADY_LOGGED_IN, + PASSPORT_AUTH_IM_OTHER_LOGIN_REMOVED, + PASSPORT_AUTH_IM_LOGIN_QUEUED, + PASSPORT_AUTH_IM_LOGIN_RESPONSE, + PASSPORT_AUTH_IM_COMMUNICATION_END, + PASSPORT_AUTH_WORLD_SESSION_CONFIRM_TO_AUTH, + PASSPORT_AUTH_WORLD_COMMUNICATION_FINISH, + PASSPORT_AUTH_WORLD_DISCONNECT, + NO_LEGO_INTERFACE, + DB_ERROR, + GM_REQUIRED, + NO_LEGO_WEBSERVICE_XML, + LEGO_WEBSERVICE_TIMEOUT, + LEGO_WEBSERVICE_ERROR, + NO_WORLD_SERVER +}; + +struct Stamp { + eStamps type{}; + uint32_t value{}; + uint64_t timestamp{}; + + Stamp() = default; + Stamp(eStamps type, uint32_t value, uint64_t timestamp = time(nullptr)){ + this->type = type; + this->value = value; + this->timestamp = timestamp; + } + + void Serialize(RakNet::BitStream& outBitStream) const; + bool Deserialize(RakNet::BitStream& inBitStream); +}; + + +enum class Language : uint32_t { + en_US, + pl_US, + de_DE, + en_GB, +}; + struct ChatMessage { uint8_t chatChannel = 0; uint16_t unknown = 0; @@ -27,6 +100,37 @@ struct ChatModerationRequest { }; namespace ClientPackets { + // Server -> client. The auth server's answer to a login (the client's clientLoginResponsePacket). + struct LoginResponse : public LUBitStream { + static constexpr const char* DEFAULT_CDN_TICKET = "00000000-0000-0000-0000-000000000000"; + + eLoginResponse responseCode{}; + LUString events[8]{}; // Event gating + uint16_t versionMajor{}; + uint16_t versionCurrent{}; + uint16_t versionMinor{}; + LUWString userKey{ 33 }; + LUString worldServerIP{ 33 }; + LUString chatServerIP{ 33 }; // Unused + uint16_t worldServerPort{}; + uint16_t chatServerPort{}; // Unused + LUString cdnKey{ 33 }; + LUString cdnTicket{ DEFAULT_CDN_TICKET, 37 }; + Language language = Language::en_US; + LUString localization{ "US", 3 }; + bool justUpgradedFromF2P{}; // Written as one byte + bool isFreeToPlay{}; // Written as one byte + uint64_t freeToPlayTimeRemaining{}; + // Written as a u16 character count followed by that many UTF-16 characters + std::string errorMessage{}; + // Written after a u32 holding their size in bytes plus 4 + std::vector stamps{}; + + LoginResponse() : LUBitStream(ServiceType::CLIENT, MessageType::Client::LOGIN_RESPONSE) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + ChatMessage HandleChatMessage(Packet* packet); PositionUpdate HandleClientPositionUpdate(Packet* packet); ChatModerationRequest HandleChatModerationRequest(Packet* packet); diff --git a/dNet/CommonPackets.cpp b/dNet/CommonPackets.cpp new file mode 100644 index 000000000..10c3939f5 --- /dev/null +++ b/dNet/CommonPackets.cpp @@ -0,0 +1,111 @@ +#include "CommonPackets.h" + +#include "dConfig.h" +#include "dServer.h" +#include "eServerDisconnectIdentifiers.h" +#include "Game.h" +#include "GeneralUtils.h" +#include "Logger.h" +#include "ServiceType.h" +#include "StringifiedEnum.h" + +#include +#include +#include + +namespace CommonPackets { + void ClientVersionConfirm::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(netVersion); + bitStream.Write(unknown); + bitStream.Write(serviceType); + bitStream.Write(padding); + bitStream.Write(processID); + bitStream.Write(port); + bitStream.Write(unknown2); + } + + bool ClientVersionConfirm::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(netVersion)); + VALIDATE_READ(bitStream.Read(unknown)); + VALIDATE_READ(bitStream.Read(serviceType)); + VALIDATE_READ(bitStream.Read(padding)); + VALIDATE_READ(bitStream.Read(processID)); + VALIDATE_READ(bitStream.Read(port)); + VALIDATE_READ(bitStream.Read(unknown2)); + return true; + } + + void ClientVersionConfirm::Handle() { + if (serviceType != ServiceType::CLIENT) LOG("WARNING: Service is not a Client!"); + if (port != sysAddr.port) LOG("WARNING: Port written in packet does not match the port the client is connecting over!"); + + LOG_DEBUG("Client Data [Version: %i, Service: %s, Process: %u, Port: %u, Sysaddr Port: %u]", netVersion, StringifiedEnum::ToString(serviceType).data(), processID, port, sysAddr.port); + + ServerVersionConfirm response; + const auto& clientNetVersionString = Game::config->GetValue("client_net_version"); + response.netVersion = GeneralUtils::TryParse(clientNetVersionString).value_or(ServerVersionConfirm::DEFAULT_NET_VERSION); + response.serviceType = static_cast(Game::server->GetServerType()); + response.Send(sysAddr); + } + + void ServerVersionConfirm::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(netVersion); + bitStream.Write(unknown); + bitStream.Write(serviceType); + bitStream.Write(trailing); + } + + bool ServerVersionConfirm::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(netVersion)); + VALIDATE_READ(bitStream.Read(unknown)); + VALIDATE_READ(bitStream.Read(serviceType)); + VALIDATE_READ(bitStream.Read(trailing)); + return true; + } + + void DisconnectNotify::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(disconnectID); + } + + bool DisconnectNotify::Deserialize(RakNet::BitStream& bitStream) { + VALIDATE_READ(bitStream.Read(disconnectID)); + return true; + } + + void GeneralNotify::Serialize(RakNet::BitStream& bitStream) const { + bitStream.Write(notifyType); + bitStream.Write(showMessageBox); + } + + bool GeneralNotify::Deserialize(RakNet::BitStream& bitStream) { + uint8_t show{}; + VALIDATE_READ(bitStream.Read(notifyType)); + VALIDATE_READ(bitStream.Read(show)); + showMessageBox = show != 0; + return true; + } + + namespace { + // Only what a client sends; the other COMMON packets go from server to client. + const std::map()>> g_Handlers = { + { MessageType::Server::VERSION_CONFIRM, []() { return std::make_unique(); } }, + }; + } + + void Handle(RakNet::BitStream& inStream, const SystemAddress& sysAddr, const uint32_t packetID) { + const auto messageID = static_cast(packetID); + const auto it = g_Handlers.find(messageID); + if (it == g_Handlers.end()) { + LOG_DEBUG("Unhandled common packet %i", packetID); + return; + } + + auto request = it->second(); + request->sysAddr = sysAddr; + if (!request->Deserialize(inStream)) { + LOG("Failed to read common packet %s", StringifiedEnum::ToString(messageID).data()); + return; + } + request->Handle(); + } +} diff --git a/dNet/CommonPackets.h b/dNet/CommonPackets.h new file mode 100644 index 000000000..b843faa93 --- /dev/null +++ b/dNet/CommonPackets.h @@ -0,0 +1,76 @@ +#ifndef COMMONPACKETS_H +#define COMMONPACKETS_H + +#include "BitStreamUtils.h" +#include "MessageType/Server.h" +#include "RakNetTypes.h" + +#include + +enum class eServerDisconnectIdentifiers : uint32_t; + +// Packets of ServiceType::COMMON (the client's MSG_SERVER_* family), used by every server the client talks to. +namespace CommonPackets { + // Client -> server. The first packet on every connection. + struct ClientVersionConfirm : public LUBitStream { + // Set by the dispatcher before Deserialize and Handle. + SystemAddress sysAddr = UNASSIGNED_SYSTEM_ADDRESS; + + uint32_t netVersion{}; + uint32_t unknown{}; // Not used by DLU + ServiceType serviceType{}; + uint16_t padding{}; + uint32_t processID{}; + uint16_t port{}; + LUString unknown2{ 33 }; // Not used by DLU; lu_packets calls it padding + + ClientVersionConfirm() : LUBitStream(ServiceType::COMMON, MessageType::Server::VERSION_CONFIRM) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + // Logs what the client sent and answers with a ServerVersionConfirm for this server. + void Handle() override; + }; + + // Server -> client. The answer to ClientVersionConfirm. + // The client (ServerHandShakePacket, 1.10.64) only reads netVersion, unknown and serviceType; the trailing + // 8 bytes are what DLU has always sent after them. + struct ServerVersionConfirm : public LUBitStream { + static constexpr uint32_t DEFAULT_NET_VERSION = 171022; + static constexpr uint32_t UNKNOWN_VALUE = 861228100; + static constexpr uint64_t TRAILING_VALUE = 219818307120; + + uint32_t netVersion = DEFAULT_NET_VERSION; + uint32_t unknown = UNKNOWN_VALUE; + uint32_t serviceType{}; // The server's ServiceType, written as 4 bytes + uint64_t trailing = TRAILING_VALUE; + + ServerVersionConfirm() : LUBitStream(ServiceType::COMMON, MessageType::Server::VERSION_CONFIRM) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // Server -> client. Tells the client why it is about to be disconnected. + struct DisconnectNotify : public LUBitStream { + eServerDisconnectIdentifiers disconnectID{}; + + DisconnectNotify() : LUBitStream(ServiceType::COMMON, MessageType::Server::DISCONNECT_NOTIFY) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // Server -> client. Layout from the 1.10.64 client (PacketHandler_MSG_SERVER_GENERAL_NOTIFY, ServerGeneralNotifyPacket): + // notifyType 0 is "just logged off a duplicate account"; showMessageBox shows the localized text. DLU does not send it yet. + struct GeneralNotify : public LUBitStream { + int32_t notifyType{}; + bool showMessageBox{}; // Written as one byte + + GeneralNotify() : LUBitStream(ServiceType::COMMON, MessageType::Server::GENERAL_NOTIFY) {} + void Serialize(RakNet::BitStream& bitStream) const override; + bool Deserialize(RakNet::BitStream& bitStream) override; + }; + + // Handles a ServiceType::COMMON packet whose header has already been read from inStream. + void Handle(RakNet::BitStream& inStream, const SystemAddress& sysAddr, uint32_t packetID); +} + +#endif // COMMONPACKETS_H diff --git a/dNet/dServer.cpp b/dNet/dServer.cpp index 2f1ccfdce..ca7c8a144 100644 --- a/dNet/dServer.cpp +++ b/dNet/dServer.cpp @@ -12,6 +12,7 @@ #include "BinaryPathFinder.h" #include "BitStreamUtils.h" +#include "CommonPackets.h" #include "MasterPackets.h" #include "ZoneInstanceManager.h" #include "StringifiedEnum.h" @@ -204,9 +205,10 @@ void dServer::SendToMaster(RakNet::BitStream& bitStream) { } void dServer::Disconnect(const SystemAddress& sysAddr, eServerDisconnectIdentifiers disconNotifyID) { + CommonPackets::DisconnectNotify notify; + notify.disconnectID = disconNotifyID; RakNet::BitStream bitStream; - BitStreamUtils::WriteHeader(bitStream, ServiceType::COMMON, MessageType::Server::DISCONNECT_NOTIFY); - bitStream.Write(disconNotifyID); + notify.WritePacket(bitStream); mPeer->Send(&bitStream, SYSTEM_PRIORITY, RELIABLE_ORDERED, 0, sysAddr, false); mPeer->CloseConnection(sysAddr, true); diff --git a/dWorldServer/WorldServer.cpp b/dWorldServer/WorldServer.cpp index a92227262..d85d99221 100644 --- a/dWorldServer/WorldServer.cpp +++ b/dWorldServer/WorldServer.cpp @@ -38,6 +38,7 @@ #include #include "AuthPackets.h" +#include "CommonPackets.h" #include "BitStreamUtils.h" #include "WorldPackets.h" #include "UserManager.h" @@ -1207,9 +1208,7 @@ void HandlePacket(Packet* packet) { luBitStream.ReadHeader(inStream); if (luBitStream.connectionType == ServiceType::COMMON) { - if (static_cast(luBitStream.internalPacketID) == MessageType::Server::VERSION_CONFIRM) { - AuthPackets::HandleHandshake(Game::server, packet); - } + CommonPackets::Handle(inStream, packet->systemAddress, luBitStream.internalPacketID); } if (luBitStream.connectionType != ServiceType::WORLD) return; diff --git a/tests/dGameTests/CMakeLists.txt b/tests/dGameTests/CMakeLists.txt index 9dbb456f0..ef417264c 100644 --- a/tests/dGameTests/CMakeLists.txt +++ b/tests/dGameTests/CMakeLists.txt @@ -13,6 +13,9 @@ list(APPEND DGAMETEST_SOURCES ${DCOMPONENTS_TESTS}) add_subdirectory(dGameMessagesTests) list(APPEND DGAMETEST_SOURCES ${DGAMEMESSAGES_TESTS}) +add_subdirectory(dNetTests) +list(APPEND DGAMETEST_SOURCES ${DNET_TESTS}) + file(COPY ${GAMEMESSAGE_TESTBITSTREAMS} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) file(COPY ${COMPONENT_TEST_DATA} DESTINATION ${CMAKE_CURRENT_BINARY_DIR}) @@ -27,7 +30,7 @@ if(APPLE) endif() target_link_libraries(dGameTests ${COMMON_LIBRARIES} GTest::gtest_main - dGame dScripts dPhysics Detour Recast tinyxml2 dWorldServer dZoneManager dChatFilter dNavigation) + dGame dScripts dPhysics Detour Recast tinyxml2 dWorldServer dZoneManager dChatFilter dNavigation bcrypt MD5) # Discover the tests gtest_discover_tests(dGameTests) diff --git a/tests/dGameTests/dNetTests/CMakeLists.txt b/tests/dGameTests/dNetTests/CMakeLists.txt new file mode 100644 index 000000000..6c8a5d642 --- /dev/null +++ b/tests/dGameTests/dNetTests/CMakeLists.txt @@ -0,0 +1,9 @@ +SET(DNET_TESTS + "CommonAuthPacketsTests.cpp") + +# Get the folder name and prepend it to the files above +get_filename_component(thisFolderName ${CMAKE_CURRENT_SOURCE_DIR} NAME) +list(TRANSFORM DNET_TESTS PREPEND "${thisFolderName}/") + +# Export to parent scope +set(DNET_TESTS ${DNET_TESTS} PARENT_SCOPE) diff --git a/tests/dGameTests/dNetTests/CommonAuthPacketsTests.cpp b/tests/dGameTests/dNetTests/CommonAuthPacketsTests.cpp new file mode 100644 index 000000000..8b7365d9e --- /dev/null +++ b/tests/dGameTests/dNetTests/CommonAuthPacketsTests.cpp @@ -0,0 +1,299 @@ +#include "AuthPackets.h" +#include "ClientPackets.h" +#include "CommonPackets.h" +#include "GameDependencies.h" +#include "PacketTestUtils.h" +#include "Legacy/CommonAuthPacketsLegacy.h" + +#include "eLoginResponse.h" +#include "eServerDisconnectIdentifiers.h" +#include "magic_enum.hpp" + +#include +#include +#include +#include +#include + +#include + +using namespace PacketTestUtils; + +namespace { + SystemAddress TestAddress(uint16_t port = 1234) { + SystemAddress address; + address.binaryAddress = 0x0100007F; + address.port = port; + return address; + } + + // Runs a handler on a copy of the packet (header included), the way the servers receive it. + Packet MakePacket(RakNet::BitStream& bitStream, const SystemAddress& sysAddr) { + Packet packet{}; + packet.systemAddress = sysAddr; + packet.data = bitStream.GetData(); + packet.length = bitStream.GetNumberOfBytesUsed(); + packet.bitSize = bitStream.GetNumberOfBitsUsed(); + return packet; + } + + // Reads the header like the servers do and hands the rest to a dispatcher. + void Dispatch(RakNet::BitStream& packetBytes, const SystemAddress& sysAddr, const std::function& handler) { + RakNet::BitStream inStream(packetBytes.GetData(), packetBytes.GetNumberOfBytesUsed(), false); + LUBitStream header; + ASSERT_TRUE(header.ReadHeader(inStream)); + handler(inStream, sysAddr, header.internalPacketID); + } + + void ExpectSamePackets(const std::vector& expected, const std::vector& actual) { + ASSERT_FALSE(expected.empty()); + ASSERT_EQ(expected.size(), actual.size()); + for (size_t i = 0; i < expected.size(); i++) { + EXPECT_PACKET_EQ(FromCapture(expected[i]), FromCapture(actual[i])); + EXPECT_EQ(expected[i].sysAddr, actual[i].sysAddr); + EXPECT_EQ(expected[i].broadcast, actual[i].broadcast); + } + } + + // Runs both, retrying if the second changed while they ran (stamps carry time(nullptr)). + void ExpectSameOutput(const std::function& legacy, const std::function& converted) { + for (int attempt = 0; attempt < 3; attempt++) { + const auto before = std::time(nullptr); + Game::randomEngine.seed(1234); + const auto expected = Capture(legacy); + Game::randomEngine.seed(1234); + const auto actual = Capture(converted); + if (before != std::time(nullptr)) continue; + ExpectSamePackets(expected, actual); + return; + } + FAIL() << "the clock kept changing seconds while comparing"; + } + + template + T RoundTrip(const T& packet) { + RakNet::BitStream first; + packet.WritePacket(first); + T copy; + EXPECT_TRUE(copy.ReadHeader(first)); + EXPECT_TRUE(copy.Deserialize(first)); + EXPECT_EQ(first.GetNumberOfUnreadBits(), 0); + RakNet::BitStream second; + copy.WritePacket(second); + EXPECT_PACKET_EQ(FromBitStream(first), FromBitStream(second)); + return copy; + } + + template + void ExpectTruncatedFails(const T& packet) { + RakNet::BitStream full; + packet.WritePacket(full); + const auto bytes = full.GetNumberOfBytesUsed(); + for (uint32_t cut = 8; cut < bytes; cut++) { + RakNet::BitStream truncated(full.GetData(), cut, true); + T copy; + ASSERT_TRUE(copy.ReadHeader(truncated)); + EXPECT_FALSE(copy.Deserialize(truncated)) << "cut at " << cut << " of " << bytes; + } + } + + const std::vector g_Strings = { "", "a", "Hello World", "user_name-01", std::string(40, 'x'), std::string(300, 'y') }; +} + +class CommonAuthPacketsTests : public GameDependenciesTest { +protected: + void SetUp() override { SetUpDependencies(); } + void TearDown() override { TearDownDependencies(); } +}; + +TEST_F(CommonAuthPacketsTests, VersionConfirmHandshakeMatchesLegacy) { + for (const uint32_t netVersion : { 0u, 171022u, 0xFFFFFFFFu }) { + for (const auto serviceType : { ServiceType::CLIENT, ServiceType::AUTH, static_cast(0xFFFF) }) { + for (const uint16_t port : { uint16_t{ 0 }, uint16_t{ 1234 }, uint16_t{ 0xFFFF } }) { + CommonPackets::ClientVersionConfirm request; + request.netVersion = netVersion; + request.unknown = 0x12345678; + request.serviceType = serviceType; + request.processID = 4321; + request.port = port; + request.unknown2 = LUString("127.0.0.1"); + RakNet::BitStream bytes; + request.WritePacket(bytes); + const auto sysAddr = TestAddress(); + + ExpectSameOutput( + [&] { auto packet = MakePacket(bytes, sysAddr); LegacyAuthPackets::HandleHandshake(Game::server, &packet); }, + [&] { Dispatch(bytes, sysAddr, CommonPackets::Handle); }); + + const auto copy = RoundTrip(request); + EXPECT_EQ(copy.netVersion, netVersion); + EXPECT_EQ(copy.serviceType, serviceType); + EXPECT_EQ(copy.port, port); + EXPECT_EQ(copy.unknown2.string, "127.0.0.1"); + } + } + } +} + +TEST_F(CommonAuthPacketsTests, ServerVersionConfirmMatchesLegacy) { + for (const auto serviceType : { ServiceType::AUTH, ServiceType::WORLD, ServiceType::CHAT }) { + const auto sysAddr = TestAddress(); + ExpectSameOutput( + [&] { LegacyAuthPackets::SendHandshake(Game::server, sysAddr, "ignored", 1, serviceType); }, + [&] { + CommonPackets::ServerVersionConfirm response; + response.serviceType = static_cast(serviceType); + response.Send(sysAddr); + }); + } +} + +TEST_F(CommonAuthPacketsTests, VersionConfirmGoldenBytes) { + CommonPackets::ServerVersionConfirm response; + response.serviceType = static_cast(ServiceType::WORLD); + RakNet::BitStream bytes; + response.WritePacket(bytes); + // 0x53 | COMMON u16 | VERSION_CONFIRM u32 | pad | 171022 | 861228100 | WORLD u32 | 219818307120 u64 + EXPECT_PACKET_EQ(FromHex("53 00 00 00 00 00 00 00 0e 9c 02 00 44 4c 55 33 04 00 00 00 30 2e 31 2e 33 00 00 00"), FromBitStream(bytes)); + RoundTrip(response); + + CommonPackets::ClientVersionConfirm request; + request.netVersion = 171022; + request.serviceType = ServiceType::CLIENT; + request.processID = 0x11223344; + request.port = 0x5566; + RakNet::BitStream requestBytes; + request.WritePacket(requestBytes); + std::string zeros; + for (int i = 0; i < 33; i++) zeros += " 00"; + // ... | 171022 | unknown 0 | CLIENT u16 | pad u16 | process id | port | 33 byte string + EXPECT_PACKET_EQ(FromHex("53 00 00 00 00 00 00 00 0e 9c 02 00 00 00 00 00 05 00 00 00 44 33 22 11 66 55" + zeros), FromBitStream(requestBytes)); + ExpectTruncatedFails(request); +} + +TEST_F(CommonAuthPacketsTests, DisconnectNotifyMatchesLegacy) { + for (const auto id : magic_enum::enum_values()) { + RakNet::BitStream expected; + LegacyAuthPackets::WriteDisconnectNotify(expected, id); + CommonPackets::DisconnectNotify notify; + notify.disconnectID = id; + RakNet::BitStream actual; + notify.WritePacket(actual); + EXPECT_PACKET_EQ(FromBitStream(expected), FromBitStream(actual)); + EXPECT_EQ(RoundTrip(notify).disconnectID, id); + } + + CommonPackets::DisconnectNotify kick; + kick.disconnectID = eServerDisconnectIdentifiers::KICK; + RakNet::BitStream bytes; + kick.WritePacket(bytes); + EXPECT_PACKET_EQ(FromHex("53 00 00 01 00 00 00 00 0b 00 00 00"), FromBitStream(bytes)); + ExpectTruncatedFails(kick); +} + +TEST_F(CommonAuthPacketsTests, GeneralNotifyGoldenAndRoundTrip) { + CommonPackets::GeneralNotify notify; + notify.notifyType = 0; + notify.showMessageBox = true; + RakNet::BitStream bytes; + notify.WritePacket(bytes); + EXPECT_PACKET_EQ(FromHex("53 00 00 02 00 00 00 00 00 00 00 00 01"), FromBitStream(bytes)); + const auto copy = RoundTrip(notify); + EXPECT_EQ(copy.notifyType, 0); + EXPECT_TRUE(copy.showMessageBox); + ExpectTruncatedFails(notify); +} + +TEST_F(CommonAuthPacketsTests, LoginResponseMatchesLegacy) { + // Event gating comes from the config; environment variables override it + setenv("EVENT_1", "Event One", 1); + setenv("EVENT_8", std::string(40, 'e').c_str(), 1); + setenv("VERSION_MINOR", "99", 1); + + const std::vector codes = { eLoginResponse::GENERAL_FAILED, eLoginResponse::BANNED, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, eLoginResponse::INVALID_USER, eLoginResponse::WRONG_PASS, eLoginResponse::ACCOUNT_LOCKED }; + for (const auto code : codes) { + for (const auto& text : g_Strings) { + for (const size_t stampCount : { size_t{ 0 }, size_t{ 1 }, size_t{ 5 } }) { + std::vector legacyStamps; + for (size_t i = 0; i < stampCount; i++) legacyStamps.emplace_back(static_cast(i), static_cast(i * 3), 1000 + i); + auto stamps = legacyStamps; + const auto sysAddr = TestAddress(); + ExpectSameOutput( + [&] { auto copy = legacyStamps; LegacyAuthPackets::SendLoginResponse(Game::server, sysAddr, code, text, text, 2001, "user", copy); }, + [&] { auto copy = stamps; AuthPackets::SendLoginResponse(Game::server, sysAddr, code, text, text, 2001, "user", copy); }); + } + } + } + + unsetenv("EVENT_1"); + unsetenv("EVENT_8"); + unsetenv("VERSION_MINOR"); +} + +TEST_F(CommonAuthPacketsTests, LoginResponseRoundTrip) { + ClientPackets::LoginResponse response; + response.responseCode = eLoginResponse::SUCCESS; + response.events[0] = LUString("Talk_Like_A_Pirate"); + response.versionMajor = 1; + response.versionCurrent = 10; + response.versionMinor = 64; + response.userKey = LUWString("0123456789abcdef0123456789abcdef"); + response.worldServerIP = LUString("192.168.1.2"); + response.worldServerPort = 2000; + response.errorMessage = "Something went wrong"; + response.stamps = { Stamp(eStamps::PASSPORT_AUTH_START, 0, 5), Stamp(eStamps::NO_WORLD_SERVER, 1, 6) }; + const auto copy = RoundTrip(response); + EXPECT_EQ(copy.responseCode, eLoginResponse::SUCCESS); + EXPECT_EQ(copy.events[0].string, "Talk_Like_A_Pirate"); + EXPECT_EQ(copy.versionMinor, 64); + EXPECT_EQ(copy.worldServerIP.string, "192.168.1.2"); + EXPECT_EQ(copy.worldServerPort, 2000); + EXPECT_EQ(copy.cdnTicket.string, ClientPackets::LoginResponse::DEFAULT_CDN_TICKET); + EXPECT_EQ(copy.localization.string, "US"); + EXPECT_EQ(copy.errorMessage, "Something went wrong"); + ASSERT_EQ(copy.stamps.size(), 2); + EXPECT_EQ(copy.stamps[1].type, eStamps::NO_WORLD_SERVER); + EXPECT_EQ(copy.stamps[1].timestamp, 6); + ExpectTruncatedFails(response); + + response.errorMessage.clear(); + response.stamps.clear(); + EXPECT_TRUE(RoundTrip(response).errorMessage.empty()); +} + +TEST_F(CommonAuthPacketsTests, LoginRequestMatchesLegacy) { + for (const auto& username : g_Strings) { + AuthPackets::LoginRequest request; + request.username = LUWString(username); + request.password = LUWString(std::string("hunter2"), 41); + request.localeID = LanguageCodeID::en_US; + request.clientOS = ClientOS::WINDOWS; + request.memoryStats = LUWString(std::string("Memory"), 256); + request.videoCard = LUWString(std::string("Video card"), 128); + request.numberOfProcessors = 8; + request.processorType = 586; + request.processorLevel = 6; + request.processorRevision = 0x3a09; + request.osVersionInfoSize = 148; + request.majorVersion = 6; + request.minorVersion = 1; + request.buildNumber = 7601; + request.platformID = 2; + RakNet::BitStream bytes; + request.WritePacket(bytes); + const auto sysAddr = TestAddress(); + + // The test database knows no accounts, so both answer INVALID_USER + ExpectSameOutput( + [&] { auto packet = MakePacket(bytes, sysAddr); LegacyAuthPackets::HandleLoginRequest(Game::server, &packet); }, + [&] { Dispatch(bytes, sysAddr, AuthPackets::Handle); }); + + const auto copy = RoundTrip(request); + EXPECT_EQ(copy.username.GetAsString(), username.substr(0, 33)); + EXPECT_EQ(copy.password.GetAsString(), "hunter2"); + EXPECT_EQ(copy.localeID, LanguageCodeID::en_US); + EXPECT_EQ(copy.buildNumber, 7601); + EXPECT_EQ(copy.platformID, 2); + ExpectTruncatedFails(request); + } +} diff --git a/tests/dGameTests/dNetTests/Legacy/CommonAuthPacketsLegacy.h b/tests/dGameTests/dNetTests/Legacy/CommonAuthPacketsLegacy.h new file mode 100644 index 000000000..48fce4c38 --- /dev/null +++ b/tests/dGameTests/dNetTests/Legacy/CommonAuthPacketsLegacy.h @@ -0,0 +1,339 @@ +#ifndef COMMONAUTHPACKETSLEGACY_H +#define COMMONAUTHPACKETSLEGACY_H + +// FROZEN ORACLE - DO NOT EDIT. +// Verbatim copies of the hand written functions that CommonPackets / AuthPackets::LoginRequest / +// ClientPackets::LoginResponse replaced (dNet/AuthPackets.cpp and dNet/dServer.cpp before the conversion). +// Only the namespace changed (and calls to its own functions name the new namespace). +// The byte-equality tests run the same inputs through these and through the new structs and require +// identical bytes, so the wire format is pinned even after the production code is deleted. + +#include "AuthPackets.h" +#include "BitStreamUtils.h" +#include "ClientPackets.h" +#include "Database.h" +#include "dClient/ClientVersion.h" +#include "dCommonVars.h" +#include "dConfig.h" +#include "dServer.h" +#include "eGameMasterLevel.h" +#include "eLoginResponse.h" +#include "eServerDisconnectIdentifiers.h" +#include "Game.h" +#include "GeneralUtils.h" +#include "Logger.h" +#include "MD5.h" +#include "MessageType/Client.h" +#include "MessageType/Master.h" +#include "MessageType/Server.h" +#include "ServiceType.h" +#include "StringifiedEnum.h" +#include "ZoneInstanceManager.h" + +#include + +#include +#include +#include +#include + +namespace LegacyAuthPackets { + inline std::vector claimCodes; + +inline std::string CleanReceivedString(const std::string& str) { + std::string toReturn = str; + const auto removed = std::ranges::find_if(toReturn, [](unsigned char c) { return isprint(c) == 0 && isblank(c) == 0; }); + toReturn.erase(removed, toReturn.end()); + return toReturn; +} + +inline void SendHandshake(dServer* server, const SystemAddress& sysAddr, const std::string& nextServerIP, uint16_t nextServerPort, const ServiceType serverType) { + RakNet::BitStream bitStream; + BitStreamUtils::WriteHeader(bitStream, ServiceType::COMMON, MessageType::Server::VERSION_CONFIRM); + + const auto& clientNetVersionString = Game::config->GetValue("client_net_version"); + const uint32_t clientNetVersion = GeneralUtils::TryParse(clientNetVersionString).value_or(171022); + + bitStream.Write(clientNetVersion); + bitStream.Write(861228100); + bitStream.Write(static_cast(serverType)); + bitStream.Write(219818307120); + + server->Send(bitStream, sysAddr, false); +} + +inline void HandleHandshake(dServer* server, Packet* packet) { + CINSTREAM_SKIP_HEADER + uint32_t clientVersion = 0; + inStream.Read(clientVersion); + inStream.IgnoreBytes(4); + + ServiceType serviceType; + inStream.Read(serviceType); + if (serviceType != ServiceType::CLIENT) LOG("WARNING: Service is not a Client!"); + inStream.IgnoreBytes(2); + + uint32_t processID; + inStream.Read(processID); + + uint16_t port; + inStream.Read(port); + if (port != packet->systemAddress.port) LOG("WARNING: Port written in packet does not match the port the client is connecting over!"); + + inStream.IgnoreBytes(33); + + LOG_DEBUG("Client Data [Version: %i, Service: %s, Process: %u, Port: %u, Sysaddr Port: %u]", clientVersion, StringifiedEnum::ToString(serviceType).data(), processID, port, packet->systemAddress.port); + + SendHandshake(server, packet->systemAddress, server->GetIP(), server->GetPort(), server->GetServerType()); +} + +inline void SendLoginResponse(dServer* server, const SystemAddress& sysAddr, eLoginResponse responseCode, const std::string& errorMsg, const std::string& wServerIP, uint16_t wServerPort, std::string username, std::vector& stamps) { + stamps.emplace_back(eStamps::PASSPORT_AUTH_IM_LOGIN_START, 1); + RakNet::BitStream loginResponse; + BitStreamUtils::WriteHeader(loginResponse, ServiceType::CLIENT, MessageType::Client::LOGIN_RESPONSE); + + loginResponse.Write(responseCode); + + // Event Gating + loginResponse.Write(LUString(Game::config->GetValue("event_1"))); + loginResponse.Write(LUString(Game::config->GetValue("event_2"))); + loginResponse.Write(LUString(Game::config->GetValue("event_3"))); + loginResponse.Write(LUString(Game::config->GetValue("event_4"))); + loginResponse.Write(LUString(Game::config->GetValue("event_5"))); + loginResponse.Write(LUString(Game::config->GetValue("event_6"))); + loginResponse.Write(LUString(Game::config->GetValue("event_7"))); + loginResponse.Write(LUString(Game::config->GetValue("event_8"))); + + const uint16_t version_major = + GeneralUtils::TryParse(Game::config->GetValue("version_major")).value_or(ClientVersion::major); + const uint16_t version_current = + GeneralUtils::TryParse(Game::config->GetValue("version_current")).value_or(ClientVersion::current); + const uint16_t version_minor = + GeneralUtils::TryParse(Game::config->GetValue("version_minor")).value_or(ClientVersion::minor); + + loginResponse.Write(version_major); + loginResponse.Write(version_current); + loginResponse.Write(version_minor); + + // Writes the user key + uint32_t sessionKey = GeneralUtils::GenerateRandomNumber(); + std::string userHash = std::to_string(sessionKey); + userHash = md5(userHash); + loginResponse.Write(LUWString(userHash)); + + // World Server IP + loginResponse.Write(LUString(wServerIP)); + // Chat Server IP (unused) + loginResponse.Write(LUString("")); + + // World Server Redirect port + loginResponse.Write(wServerPort); + // Char Server Redirect port (unused) + loginResponse.Write(static_cast(0)); + + // CDN Key + loginResponse.Write(LUString("")); + + // CDN Ticket + loginResponse.Write(LUString("00000000-0000-0000-0000-000000000000", 37)); + + // Language + loginResponse.Write(Language::en_US); + + // Write the localization + loginResponse.Write(LUString("US", 3)); + + loginResponse.Write(false); // Just upgraded from F2P + loginResponse.Write(false); // User is F2P + loginResponse.Write(0); // Time Remaining in F2P + + // Write custom error message + loginResponse.Write(errorMsg.length()); + loginResponse.Write(LUWString(errorMsg, static_cast(errorMsg.length()))); + + stamps.emplace_back(eStamps::PASSPORT_AUTH_WORLD_COMMUNICATION_FINISH, 1); + + loginResponse.Write((sizeof(Stamp) * stamps.size()) + sizeof(uint32_t)); + for (auto& stamp : stamps) stamp.Serialize(loginResponse); + + server->Send(loginResponse, sysAddr, false); + //Inform the master server that we've created a session for this user: + if (responseCode == eLoginResponse::SUCCESS) { + CBITSTREAM; + BitStreamUtils::WriteHeader(bitStream, ServiceType::MASTER, MessageType::Master::SET_SESSION_KEY); + bitStream.Write(sessionKey); + bitStream.Write(LUString(username)); + server->SendToMaster(bitStream); + + LOG("Set session key for user %s", username.c_str()); + } +} + +inline void HandleLoginRequest(dServer* server, Packet* packet) { + CINSTREAM_SKIP_HEADER; + + std::vector stamps; + stamps.emplace_back(eStamps::PASSPORT_AUTH_START, 0); + + LUWString usernameLUString; + inStream.Read(usernameLUString); + const auto username = usernameLUString.GetAsString(); + + LUWString password(41); + inStream.Read(password); + + LanguageCodeID locale_id; + inStream.Read(locale_id); + LOG_DEBUG("Locale ID: %s", StringifiedEnum::ToString(locale_id).data()); + + ClientOS clientOS; + inStream.Read(clientOS); + LOG_DEBUG("Operating System: %s", StringifiedEnum::ToString(clientOS).data()); + stamps.emplace_back(eStamps::PASSPORT_AUTH_CLIENT_OS, 0); + + LUWString memoryStats(256); + inStream.Read(memoryStats); + LOG_DEBUG("Memory Stats [%s]", CleanReceivedString(memoryStats.GetAsString()).c_str()); + + LUWString videoCard(128); + inStream.Read(videoCard); + LOG_DEBUG("VideoCard Info: [%s]", CleanReceivedString(videoCard.GetAsString()).c_str()); + + // Processor/CPU info + uint32_t numOfProcessors; + inStream.Read(numOfProcessors); + uint32_t processorType; + inStream.Read(processorType); + uint16_t processorLevel; + inStream.Read(processorLevel); + uint16_t processorRevision; + inStream.Read(processorRevision); + LOG_DEBUG("CPU Info: [#Processors: %i, Processor Type: %i, Processor Level: %i, Processor Revision: %i]", numOfProcessors, processorType, processorLevel, processorRevision); + + // OS Info + uint32_t osVersionInfoSize; + inStream.Read(osVersionInfoSize); + uint32_t majorVersion; + inStream.Read(majorVersion); + uint32_t minorVersion; + inStream.Read(minorVersion); + uint32_t buildNumber; + inStream.Read(buildNumber); + uint32_t platformID; + inStream.Read(platformID); + LOG_DEBUG("OS Info: [Size: %i, Major: %i, Minor %i, Buid#: %i, platformID: %i]", osVersionInfoSize, majorVersion, minorVersion, buildNumber, platformID); + + // Fetch account details + auto accountInfo = Database::Get()->GetAccountInfo(username); + + if (!accountInfo) { + LOG("No user by name %s found!", username.c_str()); + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + LegacyAuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::INVALID_USER, "", "", 2001, username, stamps); + return; + } + + // The password first: someone who doesn't know it learns nothing about the account (ban details, lock, play key), + // and a failed attempt changes nothing (an expired ban is only lifted for the real owner) + if (::bcrypt_checkpw(password.GetAsString().c_str(), accountInfo->bcryptPassword.c_str()) != 0) { + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + LegacyAuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::WRONG_PASS, "", "", 2001, username, stamps); + LOG("Wrong password used"); + return; + } + + //If we aren't running in live mode, then only GMs are allowed to enter: + if (Game::config->GetValue("closed_to_non_devs", false) && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) { + stamps.emplace_back(eStamps::GM_REQUIRED, 1); + LegacyAuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "The server is currently only open to developers.", "", 2001, username, stamps); + return; + } + + if (Game::config->GetValue("dont_use_keys") != "1" && accountInfo->maxGmLevel == eGameMasterLevel::CIVILIAN) { + //Check to see if we have a play key: + if (accountInfo->playKeyId == 0) { + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + LegacyAuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a play key associated with it!", "", 2001, username, stamps); + LOG("User %s tried to log in, but they don't have a play key.", username.c_str()); + return; + } + + //Check if the play key is _valid_: + auto playKeyStatus = Database::Get()->IsPlaykeyActive(accountInfo->playKeyId); + + if (!playKeyStatus) { + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + LegacyAuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your account doesn't have a valid play key associated with it!", "", 2001, username, stamps); + return; + } + + if (!playKeyStatus.value()) { + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + LegacyAuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::PERMISSIONS_NOT_HIGH_ENOUGH, "Your play key has been disabled.", "", 2001, username, stamps); + LOG("User %s tried to log in, but their play key was disabled", username.c_str()); + return; + } + } else if (Game::config->GetValue("dont_use_keys") == "1" || accountInfo->maxGmLevel > eGameMasterLevel::CIVILIAN){ + stamps.emplace_back(eStamps::PASSPORT_AUTH_BYPASS, 1); + } + + // A temporary ban that has run out is lifted as the player logs in + if (accountInfo->banned && accountInfo->banExpires > 0 && accountInfo->banExpires <= static_cast(std::time(nullptr))) { + Database::Get()->SetAccountBan(accountInfo->id, false, 0, ""); + Database::Get()->InsertAccountNote({ 0, accountInfo->id, "unban", "Temporary ban ended", "[server]", static_cast(std::time(nullptr)) }); + accountInfo->banned = false; + LOG("Temporary ban of %s ended", username.c_str()); + } + + if (accountInfo->banned) { + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + std::string message; + if (accountInfo->banExpires > 0) { + char until[32]; + const std::time_t expires = accountInfo->banExpires; + std::strftime(until, sizeof(until), "%Y-%m-%d %H:%M UTC", std::gmtime(&expires)); + message = std::string("You are banned until ") + until + "."; + } + if (!accountInfo->banReason.empty()) message += (message.empty() ? "" : " ") + std::string("Reason: ") + accountInfo->banReason; + LegacyAuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::BANNED, message, "", 2001, username, stamps); + return; + } + + if (accountInfo->locked) { + stamps.emplace_back(eStamps::PASSPORT_AUTH_ERROR, 1); + LegacyAuthPackets::SendLoginResponse(server, packet->systemAddress, eLoginResponse::ACCOUNT_LOCKED, "", "", 2001, username, stamps); + return; + } + + { + SystemAddress system = packet->systemAddress; //Copy the sysAddr before the Packet gets destroyed from main + + // Where accounts log in from, so staff can see accounts that share a connection (log_login_addresses, on by default) + if (Game::config->GetValue("log_login_addresses") != "0") { + Database::Get()->RecordLoginAddress(accountInfo->id, system.ToString(false), static_cast(std::time(nullptr))); + } + + if (!server->GetIsConnectedToMaster()) { + stamps.emplace_back(eStamps::PASSPORT_AUTH_WORLD_DISCONNECT, 1); + LegacyAuthPackets::SendLoginResponse(server, system, eLoginResponse::GENERAL_FAILED, "", "", 0, username, stamps); + return; + } + stamps.emplace_back(eStamps::PASSPORT_AUTH_WORLD_SESSION_CONFIRM_TO_AUTH, 1); + ZoneInstanceManager::Instance()->RequestZoneTransfer(server, 0, 0, false, [system, server, username, stamps](bool mythranShift, uint32_t zoneID, uint32_t zoneInstance, uint32_t zoneClone, std::string zoneIP, uint16_t zonePort) mutable { + LegacyAuthPackets::SendLoginResponse(server, system, eLoginResponse::SUCCESS, "", zoneIP, zonePort, username, stamps); + }); + } + + for(auto const code: claimCodes){ + Database::Get()->InsertRewardCode(accountInfo->id, code); + } +} + + // dServer::Disconnect, the part that built the packet (it then sent it with mPeer->Send and closed the connection). + inline void WriteDisconnectNotify(RakNet::BitStream& bitStream, eServerDisconnectIdentifiers disconNotifyID) { + BitStreamUtils::WriteHeader(bitStream, ServiceType::COMMON, MessageType::Server::DISCONNECT_NOTIFY); + bitStream.Write(disconNotifyID); + } +} + +#endif // COMMONAUTHPACKETSLEGACY_H