diff --git a/CMakeLists.txt b/CMakeLists.txt index f3c6b14a1..9ab8d5f05 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -270,6 +270,7 @@ ENDIF(EQEMU_ENABLE_BOTS) #What to build OPTION(EQEMU_BUILD_SERVER "Build the game server." ON) OPTION(EQEMU_BUILD_LOGIN "Build the login server." OFF) +OPTION(EQEMU_BUILD_HC "Build the headless client." OFF) OPTION(EQEMU_BUILD_TESTS "Build utility tests." OFF) OPTION(EQEMU_BUILD_PERL "Build Perl parser." ON) OPTION(EQEMU_BUILD_LUA "Build Lua parser." ON) @@ -379,10 +380,10 @@ INCLUDE_DIRECTORIES(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/libs/libuv/include" ) INCLUDE_DIRECTORIES(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/libs/libuv/src") INCLUDE_DIRECTORIES(SYSTEM "${CMAKE_CURRENT_SOURCE_DIR}/libs/format") -IF(EQEMU_BUILD_SERVER OR EQEMU_BUILD_LOGIN OR EQEMU_BUILD_TESTS) +IF(EQEMU_BUILD_SERVER OR EQEMU_BUILD_LOGIN OR EQEMU_BUILD_TESTS OR EQEMU_BUILD_HC) ADD_SUBDIRECTORY(common) ADD_SUBDIRECTORY(libs) -ENDIF(EQEMU_BUILD_SERVER OR EQEMU_BUILD_LOGIN OR EQEMU_BUILD_TESTS) +ENDIF(EQEMU_BUILD_SERVER OR EQEMU_BUILD_LOGIN OR EQEMU_BUILD_TESTS OR EQEMU_BUILD_HC) IF(EQEMU_BUILD_SERVER) ADD_SUBDIRECTORY(shared_memory) ADD_SUBDIRECTORY(world) @@ -395,6 +396,10 @@ IF(EQEMU_BUILD_LOGIN) ADD_SUBDIRECTORY(loginserver) ENDIF(EQEMU_BUILD_LOGIN) +IF(EQEMU_BUILD_HC) + ADD_SUBDIRECTORY(hc) +ENDIF(EQEMU_BUILD_HC) + IF(EQEMU_BUILD_TESTS) ADD_SUBDIRECTORY(tests) ENDIF(EQEMU_BUILD_TESTS) diff --git a/common/eqemu_logsys.cpp b/common/eqemu_logsys.cpp index 4c0ee1c9f..879d6cfd5 100644 --- a/common/eqemu_logsys.cpp +++ b/common/eqemu_logsys.cpp @@ -103,6 +103,7 @@ void EQEmuLogSys::LoadLogSettingsDefaults() log_settings[Logs::Crash].log_to_console = Logs::General; log_settings[Logs::MySQLError].log_to_console = Logs::General; log_settings[Logs::Login_Server].log_to_console = Logs::General; + log_settings[Logs::Headless_Client].log_to_console = Logs::General; /* Declare process file names for log writing If there is no process_file_name declared, no log file will be written, simply @@ -119,6 +120,8 @@ void EQEmuLogSys::LoadLogSettingsDefaults() platform_file_name = "login"; else if (EQEmuLogSys::log_platform == EQEmuExePlatform::ExePlatformLaunch) platform_file_name = "launcher"; + else if (EQEmuLogSys::log_platform == EQEmuExePlatform::ExePlatformHC) + platform_file_name = "hc"; } std::string EQEmuLogSys::FormatOutMessageString(uint16 log_category, const std::string &in_message) diff --git a/common/eqemu_logsys.h b/common/eqemu_logsys.h index 2cfe00eff..b3309a877 100644 --- a/common/eqemu_logsys.h +++ b/common/eqemu_logsys.h @@ -84,6 +84,7 @@ namespace Logs { Client_Server_Packet_With_Dump, Login_Server, Client_Login, + Headless_Client, MaxCategoryID /* Don't Remove this*/ }; diff --git a/common/net/daybreak_connection.cpp b/common/net/daybreak_connection.cpp index 2a5b3575a..56d8f6cff 100644 --- a/common/net/daybreak_connection.cpp +++ b/common/net/daybreak_connection.cpp @@ -105,16 +105,23 @@ void EQ::Net::DaybreakConnectionManager::Process() auto status = connection->m_status; if (status == StatusDisconnecting) { - connection->ChangeStatus(StatusDisconnected); iter = m_connections.erase(iter); + connection->ChangeStatus(StatusDisconnected); continue; } - if (status == StatusConnecting || status == StatusConnected) { + if (status == StatusConnecting) { + auto time_since_last_recv = std::chrono::duration_cast(now - connection->m_last_recv); + if ((size_t)time_since_last_recv.count() > m_options.connect_stale_ms) { + iter = m_connections.erase(iter); + connection->ChangeStatus(StatusDisconnected); + continue; + } + } else if (status == StatusConnected) { auto time_since_last_recv = std::chrono::duration_cast(now - connection->m_last_recv); if ((size_t)time_since_last_recv.count() > m_options.stale_connection_ms) { - connection->ChangeStatus(StatusDisconnected); iter = m_connections.erase(iter); + connection->ChangeStatus(StatusDisconnected); continue; } } @@ -288,6 +295,9 @@ EQ::Net::DaybreakConnection::DaybreakConnection(DaybreakConnectionManager *owner m_buffered_packets_length = 0; m_resend_delay = m_owner->m_options.resend_delay_ms; m_rolling_ping = 100; + m_combined.reset(new char[512]); + m_combined[0] = 0; + m_combined[1] = OP_Combined; m_last_session_stats = Clock::now(); } @@ -554,6 +564,23 @@ void EQ::Net::DaybreakConnection::ProcessDecodedPacket(const Packet &p) break; } + case OP_SessionResponse: + { + if (m_status == StatusConnecting) { + auto reply = p.GetSerialize(0); + + if (m_connect_code == reply.connect_code) { + m_encode_key = reply.encode_key; + m_crc_bytes = reply.crc_bytes; + m_encode_passes[0] = (DaybreakEncodeType)reply.encode_pass1; + m_encode_passes[1] = (DaybreakEncodeType)reply.encode_pass2; + m_max_packet_size = reply.max_packet_size; + ChangeStatus(StatusConnected); + } + } + break; + } + case OP_Packet: case OP_Packet2: case OP_Packet3: diff --git a/common/net/daybreak_connection.h b/common/net/daybreak_connection.h index 91ce69932..f5f349860 100644 --- a/common/net/daybreak_connection.h +++ b/common/net/daybreak_connection.h @@ -221,8 +221,9 @@ namespace EQ resend_delay_ms = 25; resend_delay_factor = 1.5; stats_delay_ms = 9000; - connect_delay_ms = 1000; + connect_delay_ms = 250; stale_connection_ms = 30000; + connect_stale_ms = 5000; crc_length = 2; max_packet_size = 512; encode_passes[0] = DaybreakEncodeType::EncodeNone; @@ -242,6 +243,7 @@ namespace EQ size_t resend_delay_ms; size_t stats_delay_ms; size_t connect_delay_ms; + size_t connect_stale_ms; size_t stale_connection_ms; size_t crc_length; size_t hold_size; diff --git a/common/net/servertalk_legacy_client_connection.cpp b/common/net/servertalk_legacy_client_connection.cpp index 34dd6af2a..67d000f64 100644 --- a/common/net/servertalk_legacy_client_connection.cpp +++ b/common/net/servertalk_legacy_client_connection.cpp @@ -19,12 +19,15 @@ EQ::Net::ServertalkLegacyClient::~ServertalkLegacyClient() void EQ::Net::ServertalkLegacyClient::Send(uint16_t opcode, EQ::Net::Packet &p) { + if (!m_connection) + return; + EQ::Net::DynamicPacket out; out.PutUInt16(0, opcode); - out.PutUInt16(2, p.Length()); + out.PutUInt16(2, p.Length() + 4); out.PutPacket(4, p); - InternalSend(ServertalkMessage, out); + m_connection->Write((const char*)out.Data(), out.Length()); } void EQ::Net::ServertalkLegacyClient::SendPacket(ServerPacket *p) @@ -75,21 +78,6 @@ void EQ::Net::ServertalkLegacyClient::ProcessData(EQ::Net::TCPConnection *c, con ProcessReadBuffer(); } -void EQ::Net::ServertalkLegacyClient::InternalSend(ServertalkPacketType type, EQ::Net::Packet &p) -{ - if (!m_connection) - return; - - EQ::Net::DynamicPacket out; - out.PutUInt32(0, (uint32_t)p.Length()); - out.PutUInt8(4, (uint8_t)type); - if (p.Length() > 0) { - out.PutPacket(5, p); - } - - m_connection->Write((const char*)out.Data(), out.Length()); -} - void EQ::Net::ServertalkLegacyClient::ProcessReadBuffer() { size_t current = 0; @@ -110,7 +98,12 @@ void EQ::Net::ServertalkLegacyClient::ProcessReadBuffer() } opcode = *(uint16_t*)&m_buffer[current]; - length = *(uint16_t*)&m_buffer[current + 2] - 4; + length = *(uint16_t*)&m_buffer[current + 2]; + if (length < 4) { + break; + } + + length -= 4; if (current + 4 + length > total) { break; diff --git a/common/net/servertalk_legacy_client_connection.h b/common/net/servertalk_legacy_client_connection.h index b4c9a23f3..4548ad16c 100644 --- a/common/net/servertalk_legacy_client_connection.h +++ b/common/net/servertalk_legacy_client_connection.h @@ -25,7 +25,6 @@ namespace EQ private: void Connect(); void ProcessData(EQ::Net::TCPConnection *c, const unsigned char *data, size_t length); - void InternalSend(ServertalkPacketType type, EQ::Net::Packet &p); void ProcessReadBuffer(); std::unique_ptr m_timer; diff --git a/common/platform.h b/common/platform.h index 281402291..8eb765257 100644 --- a/common/platform.h +++ b/common/platform.h @@ -13,7 +13,8 @@ enum EQEmuExePlatform ExePlatformLaunch, ExePlatformSharedMemory, ExePlatformClientImport, - ExePlatformClientExport + ExePlatformClientExport, + ExePlatformHC }; void RegisterExecutablePlatform(EQEmuExePlatform p); diff --git a/common/servertalk.h b/common/servertalk.h index c7805e55c..b7a52750a 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -528,13 +528,13 @@ struct ServerLSPlayerZoneChange_Struct { }; struct ClientAuth_Struct { - int lsaccount_id; // ID# in login server's db - std::string name; // username in login server's db - std::string key; // the Key the client will present - int lsadmin; // login server admin level - int worldadmin; // login's suggested worldadmin level setting for this user, up to the world if they want to obey it - std::string ip; - int local; // 1 if the client is from the local network + uint32 lsaccount_id; // ID# in login server's db + char name[30]; // username in login server's db + char key[30]; // the Key the client will present + uint8 lsadmin; // login server admin level + int16 worldadmin; // login's suggested worldadmin level setting for this user, up to the world if they want to obey it + uint32 ip; + uint8 local; // 1 if the client is from the local network template void serialize(Archive &ar) diff --git a/hc/CMakeLists.txt b/hc/CMakeLists.txt new file mode 100644 index 000000000..710e4ce03 --- /dev/null +++ b/hc/CMakeLists.txt @@ -0,0 +1,24 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.8) + +SET(hc_sources + main.cpp + login.cpp + world.cpp +) + +SET(hc_headers + login.h + world.h +) + +FIND_PACKAGE(OpenSSL REQUIRED) + +INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) + +ADD_EXECUTABLE(hc ${hc_sources} ${hc_headers}) + +INSTALL(TARGETS hc RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}/bin) + +TARGET_LINK_LIBRARIES(hc ${SERVER_LIBS} ${OPENSSL_LIBRARIES}) + +SET(EXECUTABLE_OUTPUT_PATH ${PROJECT_BINARY_DIR}/bin) diff --git a/hc/login.cpp b/hc/login.cpp new file mode 100644 index 000000000..39e229d44 --- /dev/null +++ b/hc/login.cpp @@ -0,0 +1,254 @@ +#include "login.h" +#include "../common/eqemu_logsys.h" +#include + +const char* eqcrypt_block(const char *buffer_in, size_t buffer_in_sz, char* buffer_out, bool enc) { + DES_key_schedule k; + DES_cblock v; + + memset(&k, 0, sizeof(DES_key_schedule)); + memset(&v, 0, sizeof(DES_cblock)); + + if (!enc && buffer_in_sz && buffer_in_sz % 8 != 0) { + return nullptr; + } + + DES_ncbc_encrypt((const unsigned char*)buffer_in, (unsigned char*)buffer_out, (long)buffer_in_sz, &k, &v, enc); + return buffer_out; +} + +LoginConnection::LoginConnection(const std::string &username, const std::string &password, const std::string &host, int host_port, const std::string &server) +{ + m_connecting = false; + m_username = username; + m_password = password; + m_host = host; + m_host_port = host_port; + m_server = server; + + m_connection_manager.reset(new EQ::Net::DaybreakConnectionManager()); + + m_connection_manager->OnNewConnection(std::bind(&LoginConnection::OnNewConnection, this, std::placeholders::_1)); + m_connection_manager->OnConnectionStateChange(std::bind(&LoginConnection::OnStatusChangeActive, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + m_connection_manager->OnPacketRecv(std::bind(&LoginConnection::OnPacketRecv, this, std::placeholders::_1, std::placeholders::_2)); + + m_connection_manager->Connect(host, host_port); +} + +LoginConnection::~LoginConnection() +{ +} + +void LoginConnection::OnNewConnection(std::shared_ptr connection) +{ + m_connection = connection; + Log.OutF(Logs::General, Logs::Headless_Client, "Connecting..."); +} + +void LoginConnection::OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +{ + if (to == EQ::Net::StatusConnected) { + Log.OutF(Logs::General, Logs::Headless_Client, "Login connected."); + SendSessionReady(); + } + + if (to == EQ::Net::StatusDisconnected) { + Log.OutF(Logs::General, Logs::Headless_Client, "Login connection lost, reconnecting."); + m_key.clear(); + m_dbid = 0; + m_connection.reset(); + m_connection_manager->Connect(m_host, m_host_port); + } +} + +void LoginConnection::OnStatusChangeInactive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +{ + if (to == EQ::Net::StatusDisconnected) { + m_key.clear(); + m_dbid = 0; + m_connection.reset(); + } +} + +void LoginConnection::OnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p) +{ + auto opcode = p.GetUInt16(0); + switch (opcode) { + case 0x0017: //OP_ChatMessage + SendLogin(); + break; + case 0x0018: + ProcessLoginResponse(p); + break; + case 0x0019: + ProcessServerPacketList(p); + break; + case 0x0022: + ProcessServerPlayResponse(p); + break; + } +} + +void LoginConnection::Kill() +{ + m_connection_manager->OnConnectionStateChange(std::bind(&LoginConnection::OnStatusChangeInactive, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + m_key.clear(); + m_dbid = 0; + m_connection->Close(); +} + +void LoginConnection::Start() +{ + m_connection_manager->OnConnectionStateChange(std::bind(&LoginConnection::OnStatusChangeActive, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + m_connection_manager->Connect(m_host, m_host_port); +} + +void LoginConnection::SendSessionReady() +{ + EQ::Net::DynamicPacket p; + p.PutUInt16(0, 1); //OP_SessionReady + p.PutUInt32(2, 2); + + m_connection->QueuePacket(p); +} + +void LoginConnection::SendLogin() +{ + size_t buffer_len = m_username.length() + m_password.length() + 2; + std::unique_ptr buffer(new char[buffer_len]); + + strcpy(&buffer[0], m_username.c_str()); + strcpy(&buffer[m_username.length() + 1], m_password.c_str()); + + size_t encrypted_len = buffer_len; + + if (encrypted_len % 8 > 0) { + encrypted_len = ((encrypted_len / 8) + 1) * 8; + } + + EQ::Net::DynamicPacket p; + p.Resize(12 + encrypted_len); + p.PutUInt16(0, 2); //OP_Login + p.PutUInt32(2, 3); + + eqcrypt_block(&buffer[0], buffer_len, (char*)p.Data() + 12, true); + + m_connection->QueuePacket(p); +} + +void LoginConnection::SendServerRequest() +{ + EQ::Net::DynamicPacket p; + p.PutUInt16(0, 4); //OP_ServerListRequest + p.PutUInt32(2, 4); + + m_connection->QueuePacket(p); +} + +void LoginConnection::SendPlayRequest(uint32_t id) +{ + EQ::Net::DynamicPacket p; + p.PutUInt16(0, 0x000d); + p.PutUInt16(2, 5); + p.PutUInt32(4, 0); + p.PutUInt32(8, 0); + p.PutUInt32(12, id); + + m_connection->QueuePacket(p); +} + +void LoginConnection::ProcessLoginResponse(const EQ::Net::Packet &p) +{ + auto encrypt_size = p.Length() - 12; + if (encrypt_size % 8 > 0) { + encrypt_size = (encrypt_size / 8) * 8; + } + + std::unique_ptr decrypted(new char[encrypt_size]); + + eqcrypt_block((char*)p.Data() + 12, encrypt_size, &decrypted[0], false); + + EQ::Net::StaticPacket sp(&decrypted[0], encrypt_size); + auto response_error = sp.GetUInt16(1); + + if (response_error > 101) { + Log.OutF(Logs::General, Logs::Headless_Client, "Error logging in response code: {0}", response_error); + Kill(); + } + else { + m_key = sp.GetCString(12); + m_dbid = sp.GetUInt32(8); + + Log.OutF(Logs::General, Logs::Headless_Client, "Logged in successfully with dbid {0} and key {1}", m_dbid, m_key); + SendServerRequest(); + } +} + +void LoginConnection::ProcessServerPacketList(const EQ::Net::Packet &p) +{ + m_world_servers.clear(); + auto number_of_servers = p.GetUInt32(18); + size_t idx = 22; + + for (auto i = 0U; i < number_of_servers; ++i) { + WorldServer ws; + ws.address = p.GetCString(idx); + idx += (ws.address.length() + 1); + + ws.type = p.GetInt32(idx); + idx += 4; + + auto id = p.GetUInt32(idx); + idx += 4; + + ws.long_name = p.GetCString(idx); + idx += (ws.long_name.length() + 1); + + ws.lang = p.GetCString(idx); + idx += (ws.lang.length() + 1); + + ws.region = p.GetCString(idx); + idx += (ws.region.length() + 1); + + ws.status = p.GetInt32(idx); + idx += 4; + + ws.players = p.GetInt32(idx); + idx += 4; + + m_world_servers[id] = ws; + } + + for (auto server : m_world_servers) { + if (server.second.long_name.compare(m_server) == 0) { + Log.OutF(Logs::General, Logs::Headless_Client, "Found world server {0}, attempting to login.", m_server); + SendPlayRequest(server.first); + return; + } + } + + Log.OutF(Logs::General, Logs::Headless_Client, "Got response from login server but could not find world server {0} disconnecting.", m_server); + Kill(); +} + +void LoginConnection::ProcessServerPlayResponse(const EQ::Net::Packet &p) +{ + auto allowed = p.GetUInt8(12); + + if (allowed) { + auto server = p.GetUInt32(18); + auto ws = m_world_servers.find(server); + if (ws != m_world_servers.end()) { + if (m_on_can_login_world) { + m_on_can_login_world(ws->second, m_key, m_dbid); + } + + Kill(); + } + } + else { + auto message = p.GetUInt16(13); + Log.OutF(Logs::General, Logs::Headless_Client, "Failed to login to server with message {0}"); + Kill(); + } +} diff --git a/hc/login.h b/hc/login.h new file mode 100644 index 000000000..31a448b70 --- /dev/null +++ b/hc/login.h @@ -0,0 +1,56 @@ +#pragma once + +#include "../common/net/daybreak_connection.h" +#include "../common/event/timer.h" +#include + +struct WorldServer +{ + std::string long_name; + std::string address; + int type; + std::string lang; + std::string region; + int status; + int players; +}; + +class LoginConnection +{ +public: + LoginConnection(const std::string &username, const std::string &password, const std::string &host, int host_port, const std::string &server); + void OnCanLoginToWorld(std::function cb) { m_on_can_login_world = cb; } + + ~LoginConnection(); +private: + void OnNewConnection(std::shared_ptr connection); + void OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); + void OnStatusChangeInactive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); + void OnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p); + void Kill(); + void Start(); + + void SendSessionReady(); + void SendLogin(); + void SendServerRequest(); + void SendPlayRequest(uint32_t id); + void ProcessLoginResponse(const EQ::Net::Packet &p); + void ProcessServerPacketList(const EQ::Net::Packet &p); + void ProcessServerPlayResponse(const EQ::Net::Packet &p); + + std::unique_ptr m_connection_manager; + std::shared_ptr m_connection; + bool m_connecting; + std::unique_ptr m_connect_timer; + + std::string m_username; + std::string m_password; + std::string m_host; + int m_host_port; + std::string m_server; + + std::string m_key; + uint32_t m_dbid; + std::map m_world_servers; + std::function m_on_can_login_world; +}; \ No newline at end of file diff --git a/hc/main.cpp b/hc/main.cpp new file mode 100644 index 000000000..cb42eb33e --- /dev/null +++ b/hc/main.cpp @@ -0,0 +1,32 @@ +#include "../common/event/event_loop.h" +#include "../common/eqemu_logsys.h" +#include "../common/crash.h" +#include "../common/platform.h" +#include + +#include "login.h" +#include "world.h" + +EQEmuLogSys Log; + +int main() { + RegisterExecutablePlatform(ExePlatformHC); + Log.LoadLogSettingsDefaults(); + set_exception_handler(); + + Log.OutF(Logs::General, Logs::Headless_Client, "Starting EQEmu Headless Client."); + + std::unique_ptr login_connection(new LoginConnection("testuser", "testpass", "127.0.0.1", 5999, "KLS Test")); + std::unique_ptr world_connection; + login_connection->OnCanLoginToWorld([&](const WorldServer &ws, const std::string &key, uint32_t dbid) { + Log.OutF(Logs::General, Logs::Headless_Client, "Connect to world server {1} - {0}:9000", ws.address, ws.long_name); + world_connection.reset(new WorldConnection(key, dbid, ws.address)); + }); + + for (;;) { + EQ::EventLoop::Get().Process(); + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + return 0; +} diff --git a/hc/world.cpp b/hc/world.cpp new file mode 100644 index 000000000..7be7cecb4 --- /dev/null +++ b/hc/world.cpp @@ -0,0 +1,78 @@ +#include "world.h" +#include "../common/eqemu_logsys.h" + +WorldConnection::WorldConnection(const std::string &key, uint32_t dbid, const std::string &host) +{ + m_connecting = false; + m_host = host; + m_key = key; + m_dbid = dbid; + + m_connection_manager.reset(new EQ::Net::DaybreakConnectionManager()); + m_connection_manager->OnNewConnection(std::bind(&WorldConnection::OnNewConnection, this, std::placeholders::_1)); + m_connection_manager->OnConnectionStateChange(std::bind(&WorldConnection::OnStatusChangeActive, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + m_connection_manager->OnPacketRecv(std::bind(&WorldConnection::OnPacketRecv, this, std::placeholders::_1, std::placeholders::_2)); + m_connection_manager->Connect(host, 9000); +} + +WorldConnection::~WorldConnection() { +} + +void WorldConnection::OnNewConnection(std::shared_ptr connection) +{ + m_connection = connection; + Log.OutF(Logs::General, Logs::Headless_Client, "Connecting to world..."); +} + +void WorldConnection::OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +{ + if (to == EQ::Net::StatusConnected) { + Log.OutF(Logs::General, Logs::Headless_Client, "World connected."); + SendClientAuth(); + } + + if (to == EQ::Net::StatusDisconnected) { + Log.OutF(Logs::General, Logs::Headless_Client, "World connection lost, reconnecting."); + m_connection.reset(); + m_connection_manager->Connect(m_host, 9000); + } +} + +void WorldConnection::OnStatusChangeInactive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to) +{ + if (to == EQ::Net::StatusDisconnected) { + m_connection.reset(); + } +} + +void WorldConnection::OnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p) +{ + auto opcode = p.GetUInt16(0); + Log.OutF(Logs::General, Logs::Headless_Client, "Packet in:\n{0}", p.ToString()); +} + +void WorldConnection::Kill() +{ + m_connection_manager->OnConnectionStateChange(std::bind(&WorldConnection::OnStatusChangeInactive, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + m_connection->Close(); +} + +void WorldConnection::Start() +{ + m_connection_manager->OnConnectionStateChange(std::bind(&WorldConnection::OnStatusChangeActive, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); + m_connection_manager->Connect(m_host, 9000); +} + +void WorldConnection::SendClientAuth() +{ + EQ::Net::DynamicPacket p; + p.Resize(2 + 464); + + p.PutUInt16(0, 0x7a09U); + std::string dbid_str = std::to_string(m_dbid); + + p.PutCString(2, dbid_str.c_str()); + p.PutCString(2 + dbid_str.length() + 1, m_key.c_str()); + + m_connection->QueuePacket(p); +} diff --git a/hc/world.h b/hc/world.h new file mode 100644 index 000000000..7ca0e96d7 --- /dev/null +++ b/hc/world.h @@ -0,0 +1,31 @@ +#pragma once + +#include "../common/net/daybreak_connection.h" +#include "../common/event/timer.h" +#include + +class WorldConnection +{ +public: + WorldConnection(const std::string &key, uint32_t dbid, const std::string &host); + ~WorldConnection(); +private: + void OnNewConnection(std::shared_ptr connection); + void OnStatusChangeActive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); + void OnStatusChangeInactive(std::shared_ptr conn, EQ::Net::DbProtocolStatus from, EQ::Net::DbProtocolStatus to); + void OnPacketRecv(std::shared_ptr conn, const EQ::Net::Packet &p); + void Kill(); + void Start(); + + void SendClientAuth(); + + std::unique_ptr m_connection_manager; + std::shared_ptr m_connection; + bool m_connecting; + std::unique_ptr m_connect_timer; + + std::string m_host; + + std::string m_key; + uint32_t m_dbid; +}; \ No newline at end of file diff --git a/hc/zone.cpp b/hc/zone.cpp new file mode 100644 index 000000000..e69de29bb diff --git a/hc/zone.h b/hc/zone.h new file mode 100644 index 000000000..e69de29bb diff --git a/loginserver/client.cpp b/loginserver/client.cpp index cccd96d03..e2a0ccc9f 100644 --- a/loginserver/client.cpp +++ b/loginserver/client.cpp @@ -184,8 +184,6 @@ void Client::Handle_Login(const char* data, unsigned int size) return; } - status = cs_logged_in; - char *login_packet_buffer = nullptr; unsigned int db_account_id = 0; @@ -307,6 +305,8 @@ void Client::Handle_Login(const char* data, unsigned int size) connection->QueuePacket(outapp); delete outapp; + + status = cs_logged_in; } else { EQApplicationPacket *outapp = new EQApplicationPacket(OP_LoginAccepted, sizeof(LoginLoginFailed_Struct)); @@ -332,7 +332,7 @@ void Client::Handle_Play(const char* data) { if(status != cs_logged_in) { - Log.Out(Logs::General, Logs::Error, "Client sent a play request when they either were not logged in, discarding."); + Log.Out(Logs::General, Logs::Error, "Client sent a play request when they were not logged in, discarding."); return; } @@ -372,7 +372,6 @@ void Client::SendPlayResponse(EQApplicationPacket *outapp) // server_log->LogPacket(log_network_trace, (const char*)outapp->pBuffer, outapp->size); } connection->QueuePacket(outapp); - status = cs_logged_in; } void Client::GenerateKey() diff --git a/loginserver/world_server.cpp b/loginserver/world_server.cpp index 5ef0300f5..fb171bea3 100644 --- a/loginserver/world_server.cpp +++ b/loginserver/world_server.cpp @@ -504,11 +504,11 @@ void WorldServer::SendClientAuth(std::string ip, std::string account, std::strin EQ::Net::DynamicPacket outapp; ClientAuth_Struct client_auth; client_auth.lsaccount_id = account_id; - client_auth.name = account; - client_auth.key = key; + strncpy(client_auth.name, account.c_str(), 30); + strncpy(client_auth.key, key.c_str(), 30); client_auth.lsadmin = 0; client_auth.worldadmin = 0; - client_auth.ip = ip; + client_auth.ip = inet_addr(ip.c_str()); std::string client_address(ip); std::string world_address(connection->Handle()->RemoteIP()); diff --git a/world/login_server.cpp b/world/login_server.cpp index faf2a2983..5fe555d25 100644 --- a/world/login_server.cpp +++ b/world/login_server.cpp @@ -109,7 +109,7 @@ void LoginServer::ProcessLSClientAuth(uint16_t opcode, EQ::Net::Packet &p) { client_list.EnforceSessionLimit(slsca.lsaccount_id); } - client_list.CLEAdd(slsca.lsaccount_id, slsca.name.c_str(), slsca.key.c_str(), slsca.worldadmin, inet_addr(slsca.ip.c_str()), slsca.local); + client_list.CLEAdd(slsca.lsaccount_id, slsca.name, slsca.key, slsca.worldadmin, slsca.ip, slsca.local); } catch (std::exception &ex) { Log.OutF(Logs::General, Logs::Error, "Error parsing LSClientAuth packet from world.\n{0}", ex.what());