From ffb75045a45424cf2f4b421baab1203c686fa377 Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Mon, 1 Sep 2025 12:53:41 -0500 Subject: [PATCH] clean /old/login/net --- crc16.go | 88 ----- old/login/net.cpp | 842 +++++++++++++++++++++++++++++----------------- old/login/net.h | 320 +++++++++++------- opcodes.go | 56 --- packet.go | 356 -------------------- stream.go | 675 ------------------------------------- stream_factory.go | 471 -------------------------- 7 files changed, 720 insertions(+), 2088 deletions(-) delete mode 100644 crc16.go delete mode 100644 opcodes.go delete mode 100644 packet.go delete mode 100644 stream.go delete mode 100644 stream_factory.go diff --git a/crc16.go b/crc16.go deleted file mode 100644 index f7384a7..0000000 --- a/crc16.go +++ /dev/null @@ -1,88 +0,0 @@ -package eq2net - -// CRC16 calculates the CRC-16 checksum for EQ2 packets -// This matches the exact implementation from the original EQ2Emu -func CRC16(buf []byte, size int, key uint32) uint32 { - // CRC32 table used for EQ2's CRC16 calculation - intArray := [256]uint32{ - 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, - 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, - 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, - 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, - 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, - 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, - 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, - 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, - 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, - 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, - 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, - 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, - 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, - 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, - 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, - 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, - 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, - 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, - 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, - 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, - 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, - 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, - 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, - 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, - 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, - 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, - 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, - 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, - 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, - 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, - 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, - 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D, - } - - ecx := key - eax := ecx - - eax = ^eax - eax &= 0xFF - eax = intArray[eax] - eax ^= 0x00FFFFFF - edx := ecx - edx = edx >> 8 - edx = edx ^ eax - eax = eax >> 8 - edx &= 0xFF - eax &= 0x00FFFFFF - eax ^= intArray[edx] - edx = ecx - edx = edx >> 0x10 - edx ^= eax - eax = eax >> 8 - edx &= 0xFF - esi := intArray[edx] - eax &= 0x00FFFFFF - eax ^= esi - ecx = ecx >> 0x18 - ecx ^= eax - ecx &= 0xFF - esi = intArray[ecx] - eax = eax >> 8 - eax &= 0x00FFFFFF - eax ^= esi - - for x := range size { - edx := uint32(0) - edx = uint32(buf[x]) & 0x00FF - edx ^= eax - eax = eax >> 8 - edx &= 0xFF - edi := intArray[edx] - eax &= 0x00FFFFFF - eax ^= edi - } - return ^eax -} - -// CalculateCRC16 is a convenience wrapper -func CalculateCRC16(data []byte, key uint32) uint16 { - return uint16(CRC16(data, len(data), key)) -} diff --git a/old/login/net.cpp b/old/login/net.cpp index fe6b8cd..06c6e10 100644 --- a/old/login/net.cpp +++ b/old/login/net.cpp @@ -4,360 +4,574 @@ This file is part of EQ2Emulator. */ + +// Debug and common includes #include "../common/debug.h" +// Standard library includes +#include +#include +#include +#include +#include +#include +#include +#include #include -#include -#include +#include +#include +#include #include - #include #include -#include +#include +#include +#include +#include +// Project common includes #include "../common/queue.h" #include "../common/timer.h" - #include "../common/seperator.h" - -#include "net.h" -#include "client.h" - -#include "LoginDatabase.h" -#include "LWorld.h" #include "../common/packet_functions.h" #include "../common/EQStreamFactory.h" #include "../common/MiscFunctions.h" #include "../common/version.h" - #include "../common/PacketStruct.h" #include "../common/DataBuffer.h" #include "../common/ConfigReader.h" #include "../common/Log.h" #include "../common/JsonParser.h" #include "../common/Common_Defines.h" +#include "../common/CRC16.h" +#include "../common/timer.h" +#include "../common/unix.h" -#ifdef WIN32 - #define snprintf _snprintf - #define vsnprintf _vsnprintf - #define strncasecmp _strnicmp - #define strcasecmp _stricmp - #include -#else - #include - #include "../common/unix.h" -#endif -EQStreamFactory eqsf(LoginStream); -mapEQOpcodeManager; -//TCPServer eqns(5999); -NetConnection net; -ClientList client_list; -LWorldList world_list; -LoginDatabase database; -ConfigReader configReader; -map EQOpcodeVersions; -Timer statTimer(60000); +// Login server includes +#include "net.h" +#include "client.h" +#include "LoginDatabase.h" +#include "LWorld.h" +// Global instances +EQStreamFactory eqsf(LoginStream); // Factory for creating EQ network streams +std::map EQOpcodeManager; // Maps version to opcode manager +NetConnection net; // Main network connection instance +ClientList client_list; // List of connected clients +LWorldList world_list; // List of connected world servers +LoginDatabase database; // Database connection manager +ConfigReader configReader; // Configuration file reader +std::map EQOpcodeVersions; // Maps client versions to opcode versions +Timer statTimer(60000); // Timer for periodic statistics updates (1 minute) + +// Global control flag for main loop volatile bool RunLoops = true; + +// Forward declarations bool ReadLoginConfig(); -#ifdef PUBLICLOGIN -char version[200], consoletitle[200]; -#endif -#include "../common/timer.h" - -#include "../common/CRC16.h" -#include - -int main(int argc, char** argv){ -#ifdef _DEBUG - _CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); -#endif - if (signal(SIGINT, CatchSignal) == SIG_ERR) { - cerr << "Could not set signal handler" << endl; - } - - LogStart(); - - LogParseConfigs(); - net.WelcomeHeader(); - - srand(time(NULL)); - - if(!net.ReadLoginConfig()) - return 1; - - net.InitWebServer(net.GetWebLoginAddress(), net.GetWebLoginPort(), net.GetWebCertFile(), net.GetWebKeyFile(), net.GetWebKeyPassword(), net.GetWebHardcodeUser(), net.GetWebHardcodePassword()); - - const char* structList[] = { "CommonStructs.xml", "LoginStructs.xml" }; - - for (int s = 0; s < sizeof(structList) / sizeof(const char*); s++) - { - LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s..", structList[s]); - if (configReader.processXML_Elements(structList[s])) - LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s completed..", structList[s]); - else - { - LogWrite(INIT__ERROR, 0, "Init", "Loading Structs File %s FAILED!", structList[s]); - return 1; - } - } - - - LogWrite(INIT__INFO, 0, "Init", "Initialize World List.."); - world_list.Init(); - - if(eqsf.listen_ip_address) - LogWrite(INIT__INFO, 0, "Init", "Login server listening on %s port %i", eqsf.listen_ip_address, net.GetPort()); - else - LogWrite(INIT__INFO, 0, "Init", "Login server listening on port %i", net.GetPort()); - /*} - else { - cout << "EQNetworkServer.Open() error" << endl; - return 1; - }*/ - if (!eqsf.Open(net.GetPort())) { - LogWrite(INIT__ERROR, 0, "Init", "Failed to open port %i.", net.GetPort()); - return 1; - } - net.login_running = true; - net.login_uptime = getCurrentTimestamp(); - - net.UpdateWindowTitle(); - EQStream* eqs; - Timer* TimeoutTimer = new Timer(5000); - TimeoutTimer->Start(); - while(RunLoops) { - Timer::SetCurrentTime(); - while ((eqs = eqsf.Pop())) { - struct in_addr in; - in.s_addr = eqs->GetRemoteIP(); - - LogWrite(LOGIN__INFO, 0, "Login", "New client from IP: %s on port %i", inet_ntoa(in), ntohs(eqs->GetRemotePort())); - Client* client = new Client(eqs); - eqs->SetClientVersion(0); - client_list.Add(client); - net.numclients++; - net.UpdateWindowTitle(); - } - if(TimeoutTimer->Check()){ - eqsf.CheckTimeout(); - } - if(statTimer.Check()){ - world_list.UpdateWorldStats(); - database.RemoveOldWorldServerStats(); - database.FixBugReport(); - } - client_list.Process(); - world_list.Process(); -#ifdef WIN32 - if(kbhit()) - { - int hitkey = getch(); - net.HitKey(hitkey); - } -#endif - Sleep(1); - } - //close - //eqns.Close(); - eqsf.Close(); - world_list.Shutdown(); - return 0; -} -#ifdef WIN32 -void NetConnection::HitKey(int keyhit) +/** + * @brief Check if a key has been pressed (Linux implementation) + * @return true if a key is available, false otherwise + */ +int kbhit() { - switch(keyhit) - { - case 'l': - case 'L': { - world_list.ListWorldsToConsole(); - break; - } - case 'v': - case 'V': - { - printf("========Version Info=========\n"); - printf("%s %s\n", EQ2EMU_MODULE, CURRENT_VERSION); - printf("Last Compiled on %s %s\n", COMPILE_DATE, COMPILE_TIME); - printf("=============================\n\n"); - break; - } - case 'H': - case 'h': { - printf("===========Help=============\n"); - printf("Available Commands:\n"); - printf("l = Listing of World Servers\n"); - printf("v = Login Version\n"); -// printf("0 = Kick all connected world servers\n"); - printf("============================\n\n"); - break; - } - default: - printf("Invalid Command.\n"); - break; - } -} -#endif - -void CatchSignal(int sig_num) { - cout << "Got signal " << sig_num << endl; - RunLoops = false; + struct termios oldt, newt; + int ch; + int oldf; + + // Get current terminal settings + tcgetattr(STDIN_FILENO, &oldt); + newt = oldt; + + // Disable canonical mode and echo + newt.c_lflag &= ~(ICANON | ECHO); + tcsetattr(STDIN_FILENO, TCSANOW, &newt); + + // Set non-blocking mode + oldf = fcntl(STDIN_FILENO, F_GETFL, 0); + fcntl(STDIN_FILENO, F_SETFL, oldf | O_NONBLOCK); + + ch = getchar(); + + // Restore terminal settings + tcsetattr(STDIN_FILENO, TCSANOW, &oldt); + fcntl(STDIN_FILENO, F_SETFL, oldf); + + if(ch != EOF) + { + ungetc(ch, stdin); + return 1; + } + + return 0; } -bool NetConnection::ReadLoginConfig() { - JsonParser parser(MAIN_CONFIG_FILE); - if(!parser.IsLoaded()) { - LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE); - return false; - } - std::string serverport = parser.getValue("loginconfig.serverport"); - std::string serverip = parser.getValue("loginconfig.serverip"); - - if (!parser.convertStringToUnsignedShort(serverport, port)) { - LogWrite(INIT__ERROR, 0, "Init", "Failed to translate loginconfig.serverport.."); - return false; - } - - if(serverip.size() > 0) { - eqsf.listen_ip_address = new char[serverip.size() + 1]; - strcpy(eqsf.listen_ip_address, serverip.c_str()); - } - else { - safe_delete(eqsf.listen_ip_address); - eqsf.listen_ip_address = nullptr; - } - - std::string acctcreate_str = parser.getValue("loginconfig.accountcreation"); - int16 allow_acct = 0; +/** + * @brief Main entry point for the login server + * @param argc Number of command line arguments + * @param argv Array of command line arguments + * @return Exit code (0 for success, 1 for failure) + */ +int main(int argc, char** argv) +{ + // Set up signal handler for graceful shutdown + if (signal(SIGINT, CatchSignal) == SIG_ERR) + { + std::cerr << "Could not set signal handler" << std::endl; + } + + // Initialize logging system + LogStart(); + LogParseConfigs(); + + // Display welcome header + net.WelcomeHeader(); + + // Initialize random number generator with current time + std::random_device rd; + std::mt19937 gen(rd()); + + // Read and validate login server configuration + if (!net.ReadLoginConfig()) + { + return 1; + } + + // Initialize web server if configured + net.InitWebServer( + net.GetWebLoginAddress(), + net.GetWebLoginPort(), + net.GetWebCertFile(), + net.GetWebKeyFile(), + net.GetWebKeyPassword(), + net.GetWebHardcodeUser(), + net.GetWebHardcodePassword() + ); + + // Load structure definition files for packet parsing + const std::array structList = + { + "CommonStructs.xml", + "LoginStructs.xml" + }; + + for (const auto& structFile : structList) + { + LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s..", structFile); + + if (configReader.processXML_Elements(structFile)) + { + LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s completed..", structFile); + } + else + { + LogWrite(INIT__ERROR, 0, "Init", "Loading Structs File %s FAILED!", structFile); + return 1; + } + } + + // Initialize world server list + LogWrite(INIT__INFO, 0, "Init", "Initialize World List.."); + world_list.Init(); + + // Log listening configuration + if (eqsf.listen_ip_address) + { + LogWrite(INIT__INFO, 0, "Init", "Login server listening on %s port %i", + eqsf.listen_ip_address, net.GetPort()); + } + else + { + LogWrite(INIT__INFO, 0, "Init", "Login server listening on port %i", net.GetPort()); + } + + // Open network port for incoming connections + if (!eqsf.Open(net.GetPort())) + { + LogWrite(INIT__ERROR, 0, "Init", "Failed to open port %i.", net.GetPort()); + return 1; + } + + // Set server running state + net.login_running = true; + net.login_uptime = getCurrentTimestamp(); + + // Update console window title (no-op on Linux) + net.UpdateWindowTitle(); + + // Initialize timeout timer for connection management + EQStream* eqs = nullptr; + auto timeoutTimer = std::make_unique(5000); + timeoutTimer->Start(); + + // Main server loop + while (RunLoops) + { + // Update current time for all timers + Timer::SetCurrentTime(); + + // Process new incoming connections + while ((eqs = eqsf.Pop())) + { + struct in_addr in; + in.s_addr = eqs->GetRemoteIP(); + + LogWrite(LOGIN__INFO, 0, "Login", "New client from IP: %s on port %i", + inet_ntoa(in), ntohs(eqs->GetRemotePort())); + + // Create new client instance and add to client list + auto client = std::make_unique(eqs); + eqs->SetClientVersion(0); + client_list.Add(client.release()); + net.numclients++; + net.UpdateWindowTitle(); + } + + // Check for timed out connections + if (timeoutTimer->Check()) + { + eqsf.CheckTimeout(); + } + + // Update statistics periodically + if (statTimer.Check()) + { + world_list.UpdateWorldStats(); + database.RemoveOldWorldServerStats(); + database.FixBugReport(); + } + + // Process active clients and world servers + client_list.Process(); + world_list.Process(); + + // Handle console input (Linux version) + if (kbhit()) + { + int hitkey = getchar(); + + switch (hitkey) + { + case 'l': + case 'L': + { + // List all connected world servers + world_list.ListWorldsToConsole(); + break; + } + + case 'v': + case 'V': + { + // Display version information + std::cout << "========Version Info=========\n"; + std::cout << std::format("{} {}\n", EQ2EMU_MODULE, CURRENT_VERSION); + std::cout << std::format("Last Compiled on {} {}\n", COMPILE_DATE, COMPILE_TIME); + std::cout << "=============================\n\n"; + break; + } + + case 'H': + case 'h': + { + // Display help menu + std::cout << "===========Help=============\n"; + std::cout << "Available Commands:\n"; + std::cout << "l = Listing of World Servers\n"; + std::cout << "v = Login Version\n"; + std::cout << "q = Quit server\n"; + std::cout << "============================\n\n"; + break; + } + + case 'q': + case 'Q': + { + // Quit server gracefully + std::cout << "Shutting down server...\n"; + RunLoops = false; + break; + } + + default: + { + std::cout << "Invalid Command. Press 'h' for help.\n"; + break; + } + } + } + + // Small sleep to prevent CPU spinning + std::this_thread::sleep_for(std::chrono::milliseconds(1)); + } + + // Cleanup and shutdown + eqsf.Close(); + world_list.Shutdown(); + + return 0; +} + +/** + * @brief Signal handler for graceful shutdown + * @param sig_num The signal number received + */ +void CatchSignal(int sig_num) +{ + std::cout << "Got signal " << sig_num << std::endl; + RunLoops = false; +} + +/** + * @brief Constructor for NetConnection class + * Initializes all member variables to their default values + */ +NetConnection::NetConnection() noexcept + : port_{5999} + , listening_socket_{0} + , master_address_{} + , uplink_port_{0} + , uplink_account_{} + , uplink_password_{} + , login_mode_{ServerMode::Standalone} + , uplink_wrong_version_{false} + , numclients{0} + , numservers{0} + , allow_account_creation_{true} + , expansion_flag_{0x7CFF} // Full support = 0x7CFF + , cities_flag_{0xFF} // All cities enabled + , default_subscription_level_{0xFFFFFFFF} + , enabled_races_{0xFFFF} // All races enabled + , web_login_port_{0} + , login_webserver_{nullptr} + , login_running{false} + , login_uptime{getCurrentTimestamp()} +{ + // Initialize address buffer + std::memset(address, 0, sizeof(address)); +} + +/** + * @brief Destructor for NetConnection class + * Ensures proper cleanup of web server resources + */ +NetConnection::~NetConnection() +{ + // Unique_ptr automatically handles cleanup of login_webserver +} + +/** + * @brief Reads and parses the login server configuration file + * @return true if configuration loaded successfully, false otherwise + */ +bool NetConnection::ReadLoginConfig() +{ + // Load JSON configuration file + JsonParser parser(MAIN_CONFIG_FILE); + if (!parser.IsLoaded()) + { + LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE); + return false; + } + + // Parse server port configuration + std::string serverport = parser.getValue("loginconfig.serverport"); + std::string serverip = parser.getValue("loginconfig.serverip"); + + if (!parser.convertStringToUnsignedShort(serverport, port_)) + { + LogWrite(INIT__ERROR, 0, "Init", "Failed to translate loginconfig.serverport.."); + return false; + } + + // Set listening IP address if specified + if (!serverip.empty()) + { + eqsf.listen_ip_address = new char[serverip.size() + 1]; + std::strcpy(eqsf.listen_ip_address, serverip.c_str()); + } + else + { + safe_delete(eqsf.listen_ip_address); + eqsf.listen_ip_address = nullptr; + } + + // Parse account creation setting + std::string acctcreate_str = parser.getValue("loginconfig.accountcreation"); + std::uint16_t allow_acct = 0; parser.convertStringToUnsignedShort(acctcreate_str, allow_acct); - allowAccountCreation = allow_acct > 0 ? true : false; - - std::string expflag_str = parser.getValue("loginconfig.expansionflag"); - parser.convertStringToUnsignedInt(expflag_str, expansionFlag); - - std::string citiesflag_str = parser.getValue("loginconfig.citiesflag"); - parser.convertStringToUnsignedChar(citiesflag_str, citiesFlag); - - std::string defaultsublevel_str = parser.getValue("loginconfig.defaultsubscriptionlevel"); - parser.convertStringToUnsignedInt(defaultsublevel_str, defaultSubscriptionLevel); - - std::string enableraces_str = parser.getValue("loginconfig.enabledraces"); - parser.convertStringToUnsignedInt(enableraces_str, enabledRaces); - - web_loginaddress = parser.getValue("loginconfig.webloginaddress"); - web_certfile = parser.getValue("loginconfig.webcertfile"); - web_keyfile = parser.getValue("loginconfig.webkeyfile"); - web_keypassword = parser.getValue("loginconfig.webkeypassword"); - web_hardcodeuser = parser.getValue("loginconfig.webhardcodeuser"); - web_hardcodepassword = parser.getValue("loginconfig.webhardcodepassword"); - - std::string webloginport_str = parser.getValue("loginconfig.webloginport"); - parser.convertStringToUnsignedShort(webloginport_str, web_loginport); + allow_account_creation_ = (allow_acct > 0); + + // Parse expansion and feature flags + std::string expflag_str = parser.getValue("loginconfig.expansionflag"); + parser.convertStringToUnsignedInt(expflag_str, expansion_flag_); + + std::string citiesflag_str = parser.getValue("loginconfig.citiesflag"); + parser.convertStringToUnsignedChar(citiesflag_str, cities_flag_); + + std::string defaultsublevel_str = parser.getValue("loginconfig.defaultsubscriptionlevel"); + parser.convertStringToUnsignedInt(defaultsublevel_str, default_subscription_level_); + + std::string enableraces_str = parser.getValue("loginconfig.enabledraces"); + parser.convertStringToUnsignedInt(enableraces_str, enabled_races_); + + // Parse web server configuration + web_login_address_ = parser.getValue("loginconfig.webloginaddress"); + web_cert_file_ = parser.getValue("loginconfig.webcertfile"); + web_key_file_ = parser.getValue("loginconfig.webkeyfile"); + web_key_password_ = parser.getValue("loginconfig.webkeypassword"); + web_hardcode_user_ = parser.getValue("loginconfig.webhardcodeuser"); + web_hardcode_password_ = parser.getValue("loginconfig.webhardcodepassword"); + + std::string webloginport_str = parser.getValue("loginconfig.webloginport"); + parser.convertStringToUnsignedShort(webloginport_str, web_login_port_); - LogWrite(INIT__INFO, 0, "Init", "%s loaded..", MAIN_CONFIG_FILE); + LogWrite(INIT__INFO, 0, "Init", "%s loaded..", MAIN_CONFIG_FILE); + // Initialize database connection + LogWrite(INIT__INFO, 0, "Init", "Database init begin.."); + if (!database.Init()) + { + LogWrite(INIT__ERROR, 0, "Init", "Database init FAILED!"); + LogStop(); + return false; + } - LogWrite(INIT__INFO, 0, "Init", "Database init begin.."); - //remove this when all database calls are using the new database class - if (!database.Init()) { - LogWrite(INIT__ERROR, 0, "Init", "Database init FAILED!"); - LogStop(); - return false; - } + // Load opcode definitions from database + LogWrite(INIT__INFO, 0, "Init", "Loading opcodes 2.0.."); + EQOpcodeVersions = database.GetVersions(); + + for (const auto& [version, _] : EQOpcodeVersions) + { + EQOpcodeManager[version] = new RegularOpcodeManager(); + auto opcodes = database.GetOpcodes(version); + + if (!EQOpcodeManager[version]->LoadOpcodes(&opcodes)) + { + LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!"); + return false; + } + } - LogWrite(INIT__INFO, 0, "Init", "Loading opcodes 2.0.."); - EQOpcodeVersions = database.GetVersions(); - map::iterator version_itr2; - int16 version1 = 0; - for (version_itr2 = EQOpcodeVersions.begin(); version_itr2 != EQOpcodeVersions.end(); version_itr2++) { - version1 = version_itr2->first; - EQOpcodeManager[version1] = new RegularOpcodeManager(); - map eq = database.GetOpcodes(version1); - if(!EQOpcodeManager[version1]->LoadOpcodes(&eq)) { - LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!"); - return false; - } - } - - return true; + return true; } -void NetConnection::UpdateWindowTitle(char* iNewTitle) { -#ifdef WIN32 - char tmp[500]; - if (iNewTitle) { - snprintf(tmp, sizeof(tmp), "Login: %s", iNewTitle); - } - else { - snprintf(tmp, sizeof(tmp), "%s, Version: %s: %i Server(s), %i Client(s) Connected", EQ2EMU_MODULE, CURRENT_VERSION, net.numservers, net.numclients); - } - SetConsoleTitle(tmp); -#endif +/** + * @brief Updates the console window title with server statistics + * @param iNewTitle Optional custom title string + * Note: This is a no-op on Linux, kept for API compatibility + */ +void NetConnection::UpdateWindowTitle(const char* iNewTitle) +{ + // No-op on Linux - terminal titles are handled differently + // Could potentially use ANSI escape codes if needed: + // std::cout << "\033]0;" << title << "\007"; } +/** + * @brief Displays the welcome header with ASCII art and information + */ void NetConnection::WelcomeHeader() { -#ifdef _WIN32 - HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE); - SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); -#endif - printf("Module: %s, Version: %s", EQ2EMU_MODULE, CURRENT_VERSION); -#ifdef _WIN32 - SetConsoleTextAttribute(console, FOREGROUND_YELLOW_BOLD); -#endif - printf("\n\nCopyright (C) 2007-2021 EQ2Emulator. https://www.eq2emu.com \n\n"); - printf("EQ2Emulator is free software: you can redistribute it and/or modify\n"); - printf("it under the terms of the GNU General Public License as published by\n"); - printf("the Free Software Foundation, either version 3 of the License, or\n"); - printf("(at your option) any later version.\n\n"); - printf("EQ2Emulator is distributed in the hope that it will be useful,\n"); - printf("but WITHOUT ANY WARRANTY; without even the implied warranty of\n"); - printf("MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"); - printf("GNU General Public License for more details.\n\n"); -#ifdef _WIN32 - SetConsoleTextAttribute(console, FOREGROUND_GREEN_BOLD); -#endif - printf(" /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$ \n"); - printf("| $$_____/ /$$__ $$ /$$__ $$| $$_____/ \n"); - printf("| $$ | $$ \\ $$|__/ \\ $$| $$ /$$$$$$/$$$$ /$$ /$$\n"); - printf("| $$$$$ | $$ | $$ /$$$$$$/| $$$$$ | $$_ $$_ $$| $$ | $$\n"); - printf("| $$__/ | $$ | $$ /$$____/ | $$__/ | $$ \\ $$ \\ $$| $$ | $$\n"); - printf("| $$ | $$/$$ $$| $$ | $$ | $$ | $$ | $$| $$ | $$\n"); - printf("| $$$$$$$$| $$$$$$/| $$$$$$$$| $$$$$$$$| $$ | $$ | $$| $$$$$$/\n"); - printf("|________/ \\____ $$$|________/|________/|__/ |__/ |__/ \\______/ \n"); - printf(" \\__/ \n\n"); -#ifdef _WIN32 - SetConsoleTextAttribute(console, FOREGROUND_MAGENTA_BOLD); -#endif - printf(" Website : https://eq2emu.com \n"); - printf(" Wiki : https://wiki.eq2emu.com \n"); - printf(" Git : https://git.eq2emu.com \n"); - printf(" Discord : https://discord.gg/5Cavm9NYQf \n\n"); -#ifdef _WIN32 - SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD); -#endif - printf("For more detailed logging, modify 'Level' param the log_config.xml file.\n\n"); -#ifdef _WIN32 - SetConsoleTextAttribute(console, FOREGROUND_WHITE); -#endif + // Use ANSI color codes for Linux terminal + const char* WHITE_BOLD = "\033[1;37m"; + const char* YELLOW_BOLD = "\033[1;33m"; + const char* GREEN_BOLD = "\033[1;32m"; + const char* MAGENTA_BOLD = "\033[1;35m"; + const char* RESET = "\033[0m"; - fflush(stdout); + // Display module and version information + std::cout << WHITE_BOLD; + std::cout << std::format("Module: {}, Version: {}", EQ2EMU_MODULE, CURRENT_VERSION); + + // Display copyright and license information + std::cout << YELLOW_BOLD; + std::cout << "\n\nCopyright (C) 2007-2021 EQ2Emulator. https://www.eq2emu.com \n\n"; + std::cout << "EQ2Emulator is free software: you can redistribute it and/or modify\n"; + std::cout << "it under the terms of the GNU General Public License as published by\n"; + std::cout << "the Free Software Foundation, either version 3 of the License, or\n"; + std::cout << "(at your option) any later version.\n\n"; + std::cout << "EQ2Emulator is distributed in the hope that it will be useful,\n"; + std::cout << "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"; + std::cout << "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"; + std::cout << "GNU General Public License for more details.\n\n"; + + // Display ASCII art logo + std::cout << GREEN_BOLD; + std::cout << " /$$$$$$$$ /$$$$$$ /$$$$$$ /$$$$$$$$ \n"; + std::cout << "| $$_____/ /$$__ $$ /$$__ $$| $$_____/ \n"; + std::cout << "| $$ | $$ \\ $$|__/ \\ $$| $$ /$$$$$$/$$$$ /$$ /$$\n"; + std::cout << "| $$$$$ | $$ | $$ /$$$$$$/| $$$$$ | $$_ $$_ $$| $$ | $$\n"; + std::cout << "| $$__/ | $$ | $$ /$$____/ | $$__/ | $$ \\ $$ \\ $$| $$ | $$\n"; + std::cout << "| $$ | $$/$$ $$| $$ | $$ | $$ | $$ | $$| $$ | $$\n"; + std::cout << "| $$$$$$$$| $$$$$$/| $$$$$$$$| $$$$$$$$| $$ | $$ | $$| $$$$$$/\n"; + std::cout << "|________/ \\____ $$$|________/|________/|__/ |__/ |__/ \\______/ \n"; + std::cout << " \\__/ \n\n"; + + // Display community links + std::cout << MAGENTA_BOLD; + std::cout << " Website : https://eq2emu.com \n"; + std::cout << " Wiki : https://wiki.eq2emu.com \n"; + std::cout << " Git : https://git.eq2emu.com \n"; + std::cout << " Discord : https://discord.gg/5Cavm9NYQf \n\n"; + + std::cout << WHITE_BOLD; + std::cout << "For more detailed logging, modify 'Level' param the log_config.xml file.\n\n"; + + // Reset terminal colors + std::cout << RESET; + std::cout.flush(); } -void NetConnection::InitWebServer(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password) { - if(web_ipaddr.size() > 0 && web_port > 0) { - try { - login_webserver = new WebServer(web_ipaddr, web_port, cert_file, key_file, key_password, hardcode_user, hardcode_password); - - login_webserver->register_route("/status", NetConnection::Web_loginhandle_status); - login_webserver->register_route("/worlds", NetConnection::Web_loginhandle_worlds); - login_webserver->run(); - LogWrite(INIT__INFO, 0, "Init", "Login Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port); - } - catch (const std::exception& e) { - LogWrite(INIT__ERROR, 0, "Init", "Login Web Server failed to listen on %s:%u due to reason %s", web_ipaddr.c_str(), web_port, e.what()); - } - } +/** + * @brief Initializes the web server for HTTP/HTTPS API access + * @param web_ipaddr IP address to bind to + * @param web_port Port number for web server + * @param cert_file SSL certificate file path + * @param key_file SSL key file path + * @param key_password Password for SSL key + * @param hardcode_user Basic auth username + * @param hardcode_password Basic auth password + */ +void NetConnection::InitWebServer(std::string_view web_ipaddr, + std::uint16_t web_port, + std::string_view cert_file, + std::string_view key_file, + std::string_view key_password, + std::string_view hardcode_user, + std::string_view hardcode_password) +{ + // Only initialize if address and port are configured + if (!web_ipaddr.empty() && web_port > 0) + { + try + { + // Create web server instance with SSL configuration + login_webserver_ = std::make_unique( + std::string(web_ipaddr), + web_port, + std::string(cert_file), + std::string(key_file), + std::string(key_password), + std::string(hardcode_user), + std::string(hardcode_password) + ); + + // Register API routes + login_webserver_->register_route("/status", NetConnection::Web_loginhandle_status); + login_webserver_->register_route("/worlds", NetConnection::Web_loginhandle_worlds); + + // Start web server + login_webserver_->run(); + + LogWrite(INIT__INFO, 0, "Init", "Login Web Server is listening on %s:%u..", + web_ipaddr.data(), web_port); + } + catch (const std::exception& e) + { + LogWrite(INIT__ERROR, 0, "Init", "Login Web Server failed to listen on %s:%u due to reason %s", + web_ipaddr.data(), web_port, e.what()); + } + } } \ No newline at end of file diff --git a/old/login/net.h b/old/login/net.h index 0f7b628..0cff676 100644 --- a/old/login/net.h +++ b/old/login/net.h @@ -4,144 +4,208 @@ This file is part of EQ2Emulator. */ -#ifdef WIN32 -#define WIN32_LEAN_AND_MEAN - #include - #include -#else - #include - #include - #include - #include -#endif -//#include -#include -#include +#pragma once + +// Linux network headers +#include +#include +#include +#include + +// Standard library includes #include +#include +#include +#include +#include +// Project includes #include "../common/types.h" #include "../common/Web/WebServer.h" #include "../common/MiscFunctions.h" -void CatchSignal(int sig_num); -enum eServerMode { Standalone, Master, Slave, Mesh }; +// Forward declarations +class LWorld; +/** + * @brief Signal handler for graceful shutdown + * @param sig_num Signal number received + */ +void CatchSignal(int sig_num); + +/** + * @brief Server operation modes + */ +enum class ServerMode : std::uint8_t +{ + Standalone, // Server operates independently + Master, // Server acts as master node + Slave, // Server acts as slave node + Mesh // Server participates in mesh network +}; + +/** + * @brief Main network connection manager for the login server + * + * This class handles all network operations for the login server including: + * - Client connections and authentication + * - World server connections + * - Configuration management + * - Web server integration + */ class NetConnection { public: - NetConnection() { - port = 5999; - listening_socket = 0; - memset(masteraddress, 0, sizeof(masteraddress)); - uplinkport = 0; - memset(uplinkaccount, 0, sizeof(uplinkaccount)); - memset(uplinkpassword, 0, sizeof(uplinkpassword)); - LoginMode = Standalone; - Uplink_WrongVersion = false; - numclients = 0; - numservers = 0; - allowAccountCreation = true; - - // full support = 0x7CFF - // 1 << 12 (-4096) = missing echoes of faydwer, disables Fae and Arasai (black portraits) and kelethin as starting city - // 1 << 13 (-8192) = disables sarnak (black portraits) and gorowyn as starting city - expansionFlag = 0x7CFF; // 0x4CF5 - - /* dword_1ECBA18 operand for race flag packs (sublevel 0,1,2?) -- (sublevel -1) controls starting zones omission 0xEE vs 0xCF (CF misses halas) - 1 = city of qeynos - 2 = city of freeport - 4 = city of kelethin - 8 = city of neriak - 16 = gorowyn - 32 = new halas - 64 = queens colony - 128 = outpost overlord - */ - citiesFlag = 0xFF; - - // sub_level 0xFFFFFFFF = blacks out all portraits for class alignments, considered non membership - // sub_level > 0 = class alignments still required, but portraits are viewable and race selectable - // sub_level = 2 membership, you can 'create characters on time locked servers' vs standard - // sub_level = 0 forces popup on close to web browser - defaultSubscriptionLevel = 0xFFFFFFFF; - - // disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits - enabledRaces = 0xFFFF; // 0xCFFF - - web_loginport = 0; - - login_webserver = nullptr; - - login_running = false; - login_uptime = getCurrentTimestamp(); - } - - ~NetConnection() { - safe_delete(login_webserver); - } - - void UpdateWindowTitle(char* iNewTitle = 0); - bool Init(); - void ListenNewClients(); - void HitKey(int keyhit); - char address[1024]; - int32 numclients; - int32 numservers; - - int16 GetPort() { return port; } - void SetPort(int16 in_port) { port = in_port; } - eServerMode GetLoginMode() { return LoginMode; } - - bool ReadLoginConfig(); - char* GetMasterAddress() { return masteraddress; } - int16 GetUplinkPort() { if (uplinkport != 0) return uplinkport; else return port; } - char* GetUplinkAccount() { return uplinkaccount; } - char* GetUplinkPassword() { return uplinkpassword; } - - bool IsAllowingAccountCreation() { return allowAccountCreation; } - int32 GetExpansionFlag() { return expansionFlag; } - int8 GetCitiesFlag() { return citiesFlag; } - int32 GetDefaultSubscriptionLevel() { return defaultSubscriptionLevel; } - int32 GetEnabledRaces() { return enabledRaces; } - std::string GetWebLoginAddress() { return web_loginaddress; } - inline int16 GetWebLoginPort() { return web_loginport; } - std::string GetWebCertFile() { return web_certfile; } - std::string GetWebKeyFile() { return web_keyfile; } - std::string GetWebKeyPassword() { return web_keypassword; } - std::string GetWebHardcodeUser() { return web_hardcodeuser; } - std::string GetWebHardcodePassword() { return web_hardcodepassword; } - void WelcomeHeader(); - - void InitWebServer(std::string web_ipaddr, int16 web_port, std::string cert_file, std::string key_file, std::string key_password, std::string hardcode_user, std::string hardcode_password); - - static void Web_loginhandle_status(const http::request& req, http::response& res); - static void Web_loginhandle_worlds(const http::request& req, http::response& res); - - bool login_running; - std::atomic login_uptime; + /** + * @brief Default constructor - initializes all network settings to defaults + */ + NetConnection() noexcept; + + /** + * @brief Destructor - ensures proper cleanup of network resources + */ + ~NetConnection(); + + // Disable copy operations (network connection should be unique) + NetConnection(const NetConnection&) = delete; + NetConnection& operator=(const NetConnection&) = delete; + + // Enable move operations for potential future use + NetConnection(NetConnection&&) = default; + NetConnection& operator=(NetConnection&&) = default; + + /** + * @brief Updates the console window title with server statistics + * @param new_title Optional custom title (nullptr uses default format) + * Note: This is a no-op on Linux but kept for API compatibility + */ + void UpdateWindowTitle(const char* new_title = nullptr); + + /** + * @brief Initializes the network connection + * @return true if initialization successful, false otherwise + */ + bool Init(); + + /** + * @brief Listens for new client connections + */ + void ListenNewClients(); + + /** + * @brief Reads configuration from the login config file + * @return true if config loaded successfully, false otherwise + */ + bool ReadLoginConfig(); + + /** + * @brief Displays the welcome header with version and copyright info + */ + void WelcomeHeader(); + + /** + * @brief Initializes the web server for HTTP/HTTPS API access + * @param web_ipaddr IP address to bind the web server to + * @param web_port Port number for the web server + * @param cert_file Path to SSL certificate file + * @param key_file Path to SSL key file + * @param key_password Password for the SSL key + * @param hardcode_user Hardcoded username for basic auth + * @param hardcode_password Hardcoded password for basic auth + */ + void InitWebServer(std::string_view web_ipaddr, + std::uint16_t web_port, + std::string_view cert_file, + std::string_view key_file, + std::string_view key_password, + std::string_view hardcode_user, + std::string_view hardcode_password); + + // Static web server request handlers + /** + * @brief Handles /status endpoint requests + * @param req HTTP request object + * @param res HTTP response object to populate + */ + static void Web_loginhandle_status(const http::request& req, + http::response& res); + + /** + * @brief Handles /worlds endpoint requests + * @param req HTTP request object + * @param res HTTP response object to populate + */ + static void Web_loginhandle_worlds(const http::request& req, + http::response& res); + + // Getter methods for configuration values + [[nodiscard]] std::uint16_t GetPort() const noexcept { return port_; } + [[nodiscard]] ServerMode GetLoginMode() const noexcept { return login_mode_; } + [[nodiscard]] const char* GetMasterAddress() const noexcept { return master_address_.data(); } + [[nodiscard]] std::uint16_t GetUplinkPort() const noexcept + { + return (uplink_port_ != 0) ? uplink_port_ : port_; + } + [[nodiscard]] const char* GetUplinkAccount() const noexcept { return uplink_account_.data(); } + [[nodiscard]] const char* GetUplinkPassword() const noexcept { return uplink_password_.data(); } + [[nodiscard]] bool IsAllowingAccountCreation() const noexcept { return allow_account_creation_; } + [[nodiscard]] std::uint32_t GetExpansionFlag() const noexcept { return expansion_flag_; } + [[nodiscard]] std::uint8_t GetCitiesFlag() const noexcept { return cities_flag_; } + [[nodiscard]] std::uint32_t GetDefaultSubscriptionLevel() const noexcept { return default_subscription_level_; } + [[nodiscard]] std::uint32_t GetEnabledRaces() const noexcept { return enabled_races_; } + [[nodiscard]] const std::string& GetWebLoginAddress() const noexcept { return web_login_address_; } + [[nodiscard]] std::uint16_t GetWebLoginPort() const noexcept { return web_login_port_; } + [[nodiscard]] const std::string& GetWebCertFile() const noexcept { return web_cert_file_; } + [[nodiscard]] const std::string& GetWebKeyFile() const noexcept { return web_key_file_; } + [[nodiscard]] const std::string& GetWebKeyPassword() const noexcept { return web_key_password_; } + [[nodiscard]] const std::string& GetWebHardcodeUser() const noexcept { return web_hardcode_user_; } + [[nodiscard]] const std::string& GetWebHardcodePassword() const noexcept { return web_hardcode_password_; } + + // Setter methods for configuration values + void SetPort(std::uint16_t port) noexcept { port_ = port; } + + // Public state variables + char address[1024]{}; // Server address buffer + std::atomic numclients{0}; // Current number of connected clients + std::atomic numservers{0}; // Current number of connected world servers + std::atomic login_running{false}; // Flag indicating if login server is running + std::atomic login_uptime{0}; // Timestamp when server started + protected: - friend class LWorld; - bool Uplink_WrongVersion; + friend class LWorld; // Allow LWorld access to protected members + + /** + * @brief Flag indicating uplink version mismatch + */ + bool uplink_wrong_version_{false}; + private: - int16 port; - int listening_socket; - char masteraddress[300]; - int16 uplinkport; - char uplinkaccount[300]; - char uplinkpassword[300]; - eServerMode LoginMode; - bool allowAccountCreation; - int32 expansionFlag; - int8 citiesFlag; - int32 defaultSubscriptionLevel; - int32 enabledRaces; - std::string web_loginaddress; - std::string web_certfile; - std::string web_keyfile; - std::string web_keypassword; - std::string web_hardcodeuser; - std::string web_hardcodepassword; - int16 web_loginport; - WebServer* login_webserver; -}; + // Network configuration + std::uint16_t port_{5999}; // Server listening port + int listening_socket_{0}; // Main listening socket descriptor + + // Master server configuration (for non-standalone modes) + std::array master_address_{}; // Master server address + std::uint16_t uplink_port_{0}; // Port for uplink connection + std::array uplink_account_{}; // Account for uplink authentication + std::array uplink_password_{}; // Password for uplink authentication + ServerMode login_mode_{ServerMode::Standalone}; // Current server operation mode + + // Account and expansion settings + bool allow_account_creation_{true}; // Allow new account creation + std::uint32_t expansion_flag_{0x7CFF}; // Enabled expansions bitfield + std::uint8_t cities_flag_{0xFF}; // Enabled starting cities bitfield + std::uint32_t default_subscription_level_{0xFFFFFFFF}; // Default subscription level for new accounts + std::uint32_t enabled_races_{0xFFFF}; // Enabled races bitfield + + // Web server configuration + std::string web_login_address_; // Web server bind address + std::string web_cert_file_; // SSL certificate file path + std::string web_key_file_; // SSL key file path + std::string web_key_password_; // SSL key password + std::string web_hardcode_user_; // Basic auth username + std::string web_hardcode_password_; // Basic auth password + std::uint16_t web_login_port_{0}; // Web server port + std::unique_ptr login_webserver_; // Web server instance +}; \ No newline at end of file diff --git a/opcodes.go b/opcodes.go deleted file mode 100644 index ab0e42a..0000000 --- a/opcodes.go +++ /dev/null @@ -1,56 +0,0 @@ -package eq2net - -// Protocol opcodes for UDP packet types -const ( - OPSessionRequest uint8 = 0x01 - OPSessionResponse uint8 = 0x02 - OPCombined uint8 = 0x03 - OPSessionDisconnect uint8 = 0x05 - OPKeepAlive uint8 = 0x06 - OPServerKeyRequest uint8 = 0x07 - OPSessionStatResponse uint8 = 0x08 - OPPacket uint8 = 0x09 - OPFragment uint8 = 0x0d - OPOutOfOrderAck uint8 = 0x11 - OPAck uint8 = 0x15 - OPAppCombined uint8 = 0x19 - OPOutOfSession uint8 = 0x1d -) - -// Application opcodes -type EmuOpcode uint16 - -const ( - OPUnknown EmuOpcode = iota - OPLoginReplyMsg - OPLoginByNumRequestMsg - OPWSLoginRequestMsg - OPESInitMsg - OPESReadyForClientsMsg - OPCreateZoneInstanceMsg - OPZoneInstanceCreateReplyMsg - OPZoneInstanceDestroyedMsg - OPExpectClientAsCharacterRequest - OPExpectClientAsCharacterReplyMs - OPZoneInfoMsg - OPCreateCharacterRequestMsg - OPDoneLoadingZoneResourcesMsg - OPDoneSendingInitialEntitiesMsg - OPDoneLoadingEntityResourcesMsg - OPDoneLoadingUIResourcesMsg - OPPredictionUpdateMsg - OPRemoteCmdMsg - OPSetRemoteCmdsMsg - OPGameWorldTimeMsg - OPMOTDMsg - OPZoneMOTDMsg - OPGuildRecruitingMemberInfo - OPGuildRecruiting - OPGuildRecruitingDetails - OPGuildRecruitingImage - OPAvatarCreatedMsg - OPAvatarDestroyedMsg - OPRequestCampMsg - OPMapRequest - OPCampStartedMsg -) \ No newline at end of file diff --git a/packet.go b/packet.go deleted file mode 100644 index 8f8eff7..0000000 --- a/packet.go +++ /dev/null @@ -1,356 +0,0 @@ -package eq2net - -import ( - "bytes" - "compress/zlib" - "encoding/binary" - "fmt" - "net" - "time" - "unsafe" -) - -// Packet represents a base packet structure -type Packet struct { - Buffer []byte - Size uint32 - SrcIP net.IP - SrcPort uint16 - DstIP net.IP - DstPort uint16 - Priority uint32 - Timestamp time.Time - Version int16 - Opcode uint16 -} - -// NewPacket creates a new packet -func NewPacket(op uint16, buf []byte) *Packet { - p := &Packet{ - Opcode: op, - Size: uint32(len(buf)), - Timestamp: time.Now(), - } - if buf != nil { - p.Buffer = make([]byte, len(buf)) - copy(p.Buffer, buf) - } - return p -} - -// SetSrcInfo sets source IP and port -func (p *Packet) SetSrcInfo(ip net.IP, port uint16) { - p.SrcIP = ip - p.SrcPort = port -} - -// SetDstInfo sets destination IP and port -func (p *Packet) SetDstInfo(ip net.IP, port uint16) { - p.DstIP = ip - p.DstPort = port -} - -// GetRawOpcode returns the raw opcode -func (p *Packet) GetRawOpcode() uint16 { - return p.Opcode -} - -// ProtocolPacket represents a protocol-level packet -type ProtocolPacket struct { - *Packet - EQ2Compressed bool - PacketPrepared bool - PacketEncrypted bool - Acked bool - SentTime int32 - AttemptCount int8 - Sequence uint16 -} - -// NewProtocolPacket creates a new protocol packet from raw data -func NewProtocolPacket(buf []byte, opcode int) *ProtocolPacket { - if len(buf) < 2 { - return nil - } - - var op uint16 - if opcode >= 0 { - op = uint16(opcode) - } else { - op = binary.BigEndian.Uint16(buf[:2]) - buf = buf[2:] - } - - pp := &ProtocolPacket{ - Packet: NewPacket(op, buf), - } - return pp -} - -// NewProtocolPacketWithOp creates a new protocol packet with specific opcode -func NewProtocolPacketWithOp(op uint16, buf []byte) *ProtocolPacket { - return &ProtocolPacket{ - Packet: NewPacket(op, buf), - } -} - -// ValidateCRC validates the CRC of a packet -func ValidateCRC(buffer []byte, length int, key uint32) bool { - // SessionRequest, SessionResponse, OutOfSession are not CRC'd - if len(buffer) >= 2 && buffer[0] == 0x00 && - (buffer[1] == OPSessionRequest || buffer[1] == OPSessionResponse || buffer[1] == OPOutOfSession) { - return true - } - - // Check for special case - if len(buffer) >= 4 && buffer[2] == 0x00 && buffer[3] == 0x19 { - return true - } - - // Calculate and compare CRC - if length < 2 { - return false - } - - compCRC := CalculateCRC16(buffer[:length-2], key) - packetCRC := binary.BigEndian.Uint16(buffer[length-2:]) - - return packetCRC == 0 || compCRC == packetCRC -} - -// Serialize serializes the protocol packet -func (p *ProtocolPacket) Serialize(offset int8) []byte { - // Calculate size including opcode - totalSize := len(p.Buffer) + 2 - if offset != 0 { - totalSize += int(offset) - } - - result := make([]byte, totalSize) - pos := 0 - - // Add offset if needed - if offset != 0 { - pos = int(offset) - } - - // Write opcode - binary.BigEndian.PutUint16(result[pos:], p.Opcode) - pos += 2 - - // Copy buffer - if p.Buffer != nil { - copy(result[pos:], p.Buffer) - } - - return result -} - -// Copy creates a copy of the protocol packet -func (p *ProtocolPacket) Copy() *ProtocolPacket { - newPacket := &ProtocolPacket{ - Packet: NewPacket(p.Opcode, p.Buffer), - EQ2Compressed: p.EQ2Compressed, - PacketPrepared: p.PacketPrepared, - PacketEncrypted: p.PacketEncrypted, - Acked: p.Acked, - SentTime: p.SentTime, - AttemptCount: p.AttemptCount, - Sequence: p.Sequence, - } - newPacket.Version = p.Version - newPacket.Priority = p.Priority - return newPacket -} - -// Combine combines two protocol packets -func (p *ProtocolPacket) Combine(rhs *ProtocolPacket) bool { - if rhs.Opcode != p.Opcode { - return false - } - - // Create new buffer with combined size - newSize := len(p.Buffer) + len(rhs.Buffer) - newBuffer := make([]byte, newSize) - - copy(newBuffer, p.Buffer) - copy(newBuffer[len(p.Buffer):], rhs.Buffer) - - p.Buffer = newBuffer - p.Size = uint32(newSize) - - return true -} - -// IsProtocolPacket checks if a buffer contains a protocol packet -func IsProtocolPacket(inBuff []byte, length uint32, trimCRC bool) bool { - if length < 2 { - return false - } - - opcode := binary.BigEndian.Uint16(inBuff) - - switch uint8(opcode & 0xFF) { - case OPSessionRequest, OPSessionResponse, OPCombined, - OPSessionDisconnect, OPKeepAlive, OPServerKeyRequest, - OPSessionStatResponse, OPPacket, OPFragment, - OPOutOfOrderAck, OPAck, OPAppCombined, OPOutOfSession: - return true - } - - return false -} - -// Decompress decompresses a buffer -func Decompress(buffer []byte, length uint32, newbuf []byte, newbufsize uint32) (uint32, error) { - reader := bytes.NewReader(buffer[:length]) - zlibReader, err := zlib.NewReader(reader) - if err != nil { - return 0, err - } - defer zlibReader.Close() - - buf := bytes.NewBuffer(newbuf[:0]) - n, err := buf.ReadFrom(zlibReader) - if err != nil { - return 0, err - } - - if uint32(n) > newbufsize { - return 0, fmt.Errorf("decompressed size exceeds buffer size") - } - - return uint32(n), nil -} - -// Compress compresses a buffer -func Compress(buffer []byte, length uint32, newbuf []byte, newbufsize uint32) (uint32, error) { - var buf bytes.Buffer - writer := zlib.NewWriter(&buf) - - _, err := writer.Write(buffer[:length]) - if err != nil { - return 0, err - } - - err = writer.Close() - if err != nil { - return 0, err - } - - compressed := buf.Bytes() - if uint32(len(compressed)) > newbufsize { - return 0, fmt.Errorf("compressed size exceeds buffer size") - } - - copy(newbuf, compressed) - return uint32(len(compressed)), nil -} - -// ChatDecode decodes chat data -func ChatDecode(buffer []byte, size int, decodeKey int) { - for i := 0; i+4 <= size; i++ { - c := buffer[i] - buffer[i] = buffer[i+2] - buffer[i+2] = c - c = buffer[i+1] - buffer[i+1] = buffer[i+3] - buffer[i+3] = c - - key := uint32(decodeKey) - p := (*uint32)(unsafe.Pointer(&buffer[i])) - *p = (*p) ^ key - i += 3 - } -} - -// ChatEncode encodes chat data -func ChatEncode(buffer []byte, size int, encodeKey int) { - for i := 0; i+4 <= size; i++ { - key := uint32(encodeKey) - p := (*uint32)(unsafe.Pointer(&buffer[i])) - *p = (*p) ^ key - - c := buffer[i] - buffer[i] = buffer[i+2] - buffer[i+2] = c - c = buffer[i+1] - buffer[i+1] = buffer[i+3] - buffer[i+3] = c - i += 3 - } -} - -// ApplicationPacket represents an application-level packet -type ApplicationPacket struct { - *Packet - EmuOpcode EmuOpcode - AppOpcodeSize uint8 -} - -// NewApplicationPacket creates a new application packet -func NewApplicationPacket(op EmuOpcode, buf []byte) *ApplicationPacket { - ap := &ApplicationPacket{ - Packet: NewPacket(0, buf), - EmuOpcode: op, - AppOpcodeSize: 2, // Default for world/zone - } - return ap -} - -// SetOpcode sets the opcode -func (p *ApplicationPacket) SetOpcode(op EmuOpcode) { - p.EmuOpcode = op - p.Opcode = uint16(op) -} - -// GetOpcode returns the opcode -func (p *ApplicationPacket) GetOpcode() EmuOpcode { - return p.EmuOpcode -} - -// Copy creates a copy of the application packet -func (p *ApplicationPacket) Copy() *ApplicationPacket { - newPacket := &ApplicationPacket{ - Packet: NewPacket(p.Opcode, p.Buffer), - EmuOpcode: p.EmuOpcode, - AppOpcodeSize: p.AppOpcodeSize, - } - newPacket.Version = p.Version - return newPacket -} - -// Serialize serializes the application packet -func (p *ApplicationPacket) Serialize() []byte { - size := len(p.Buffer) + int(p.AppOpcodeSize) - result := make([]byte, size) - - // Write opcode based on size - if p.AppOpcodeSize == 1 { - result[0] = uint8(p.Opcode) - copy(result[1:], p.Buffer) - } else { - binary.LittleEndian.PutUint16(result, p.Opcode) - copy(result[2:], p.Buffer) - } - - return result -} - -// Combine combines two application packets -func (p *ApplicationPacket) Combine(rhs *ApplicationPacket) bool { - if rhs.EmuOpcode != p.EmuOpcode { - return false - } - - newSize := len(p.Buffer) + len(rhs.Buffer) - newBuffer := make([]byte, newSize) - - copy(newBuffer, p.Buffer) - copy(newBuffer[len(p.Buffer):], rhs.Buffer) - - p.Buffer = newBuffer - p.Size = uint32(newSize) - - return true -} \ No newline at end of file diff --git a/stream.go b/stream.go deleted file mode 100644 index b387b76..0000000 --- a/stream.go +++ /dev/null @@ -1,675 +0,0 @@ -package eq2net - -import ( - "encoding/binary" - "fmt" - "net" - "sync" - "time" -) - -// StreamState represents the connection state -type StreamState int - -const ( - Established StreamState = iota - WaitClose - Closing - Disconnecting - Closed -) - -// StreamType represents different stream types -type StreamType int - -const ( - UnknownStream StreamType = iota - LoginStream - WorldStream - ZoneStream - ChatOrMailStream - ChatStream - MailStream - EQ2Stream -) - -// SessionRequest packet structure -type SessionRequest struct { - UnknownA uint32 - Session uint32 - MaxLength uint32 -} - -// SessionResponse packet structure -type SessionResponse struct { - Session uint32 - Key uint32 - UnknownA uint8 - Format uint8 - UnknownB uint8 - MaxLength uint32 - UnknownD uint32 -} - -// ClientSessionStats for tracking connection statistics -type ClientSessionStats struct { - RequestID uint16 - LastLocalDelta uint32 - AverageDelta uint32 - LowDelta uint32 - HighDelta uint32 - LastRemoteDelta uint32 - PacketsSent uint64 - PacketsReceived uint64 -} - -// ServerSessionStats for server-side statistics -type ServerSessionStats struct { - RequestID uint16 - CurrentTime uint32 - Unknown1 uint32 - ReceivedPackets uint32 - Unknown2 uint32 - SentPackets uint32 - Unknown3 uint32 - SentPackets2 uint32 - Unknown4 uint32 - ReceivedPackets2 uint32 -} - -// Stream represents a connection stream -type Stream struct { - mu sync.RWMutex - - // Connection info - remoteAddr *net.UDPAddr - remoteIP net.IP - remotePort uint16 - localAddr *net.UDPAddr - sessionID uint32 - key uint32 - state StreamState - streamType StreamType - - // Buffers - buffer [8192]byte - oversizeBuffer []byte - oversizeOffset uint32 - oversizeLength uint32 - rogueBuffer []byte - rogueBufOffset uint32 - rogueBufSize uint32 - - // Packet tracking - receivedPackets uint32 - sentPackets uint32 - nextInSeq uint16 - nextOutSeq uint16 - lastAckSent uint16 - lastAckReceived uint16 - - // Timing - lastReceiveTime time.Time - lastSendTime time.Time - avgRoundTrip time.Duration - minRoundTrip time.Duration - maxRoundTrip time.Duration - retransmitTimeout time.Duration - - // Queues - incomingQueue []*ProtocolPacket - outgoingQueue []*ProtocolPacket - sentQueue map[uint16]*ProtocolPacket - futurePackets map[uint16]*ProtocolPacket - - // Options - compressed bool - encoded bool - maxLength uint32 - opcodeSize uint8 - - // Callbacks - onPacket func(*ApplicationPacket) - onDisconnect func() -} - -// NewStream creates a new stream -func NewStream(remoteAddr *net.UDPAddr, streamType StreamType) *Stream { - s := &Stream{ - remoteAddr: remoteAddr, - remoteIP: remoteAddr.IP, - remotePort: uint16(remoteAddr.Port), - streamType: streamType, - state: Established, - sentQueue: make(map[uint16]*ProtocolPacket), - futurePackets: make(map[uint16]*ProtocolPacket), - lastReceiveTime: time.Now(), - lastSendTime: time.Now(), - retransmitTimeout: 500 * time.Millisecond, - maxLength: 512, - opcodeSize: 2, - } - - // Set opcode size based on stream type - if streamType == LoginStream || streamType == ChatStream || streamType == MailStream { - s.opcodeSize = 1 - } - - return s -} - -// SetSessionInfo sets the session ID and key -func (s *Stream) SetSessionInfo(sessionID uint32, key uint32) { - s.mu.Lock() - defer s.mu.Unlock() - s.sessionID = sessionID - s.key = key -} - -// GetSessionID returns the session ID -func (s *Stream) GetSessionID() uint32 { - s.mu.RLock() - defer s.mu.RUnlock() - return s.sessionID -} - -// GetKey returns the session key -func (s *Stream) GetKey() uint32 { - s.mu.RLock() - defer s.mu.RUnlock() - return s.key -} - -// GetState returns the current stream state -func (s *Stream) GetState() StreamState { - s.mu.RLock() - defer s.mu.RUnlock() - return s.state -} - -// SetState sets the stream state -func (s *Stream) SetState(state StreamState) { - s.mu.Lock() - defer s.mu.Unlock() - s.state = state -} - -// GetRemoteAddr returns the remote address -func (s *Stream) GetRemoteAddr() *net.UDPAddr { - return s.remoteAddr -} - -// Process processes an incoming packet -func (s *Stream) Process(data []byte) error { - s.mu.Lock() - defer s.mu.Unlock() - - s.lastReceiveTime = time.Now() - s.receivedPackets++ - - // Parse the packet - if len(data) < 2 { - return fmt.Errorf("packet too small") - } - - opcode := binary.BigEndian.Uint16(data) - - // Handle different packet types - switch uint8(opcode & 0xFF) { - case OPSessionRequest: - return s.handleSessionRequest(data) - case OPSessionResponse: - return s.handleSessionResponse(data) - case OPKeepAlive: - return s.handleKeepAlive(data) - case OPPacket: - return s.handlePacket(data) - case OPFragment: - return s.handleFragment(data) - case OPAck: - return s.handleAck(data) - case OPOutOfOrderAck: - return s.handleOutOfOrderAck(data) - case OPSessionDisconnect: - return s.handleDisconnect(data) - case OPCombined: - return s.handleCombined(data) - case OPAppCombined: - return s.handleAppCombined(data) - default: - return fmt.Errorf("unknown opcode: %02x", opcode&0xFF) - } -} - -// handleSessionRequest handles session request packets -func (s *Stream) handleSessionRequest(data []byte) error { - if len(data) < 14 { // 2 byte opcode + 12 byte SessionRequest - return fmt.Errorf("session request too small") - } - - req := &SessionRequest{} - req.UnknownA = binary.LittleEndian.Uint32(data[2:6]) - req.Session = binary.LittleEndian.Uint32(data[6:10]) - req.MaxLength = binary.LittleEndian.Uint32(data[10:14]) - - s.sessionID = req.Session - s.maxLength = req.MaxLength - - // Send session response - return s.sendSessionResponse() -} - -// handleSessionResponse handles session response packets -func (s *Stream) handleSessionResponse(data []byte) error { - if len(data) < 21 { // 2 byte opcode + 19 byte SessionResponse - return fmt.Errorf("session response too small") - } - - resp := &SessionResponse{} - resp.Session = binary.LittleEndian.Uint32(data[2:6]) - resp.Key = binary.LittleEndian.Uint32(data[6:10]) - resp.UnknownA = data[10] - resp.Format = data[11] - resp.UnknownB = data[12] - resp.MaxLength = binary.LittleEndian.Uint32(data[13:17]) - resp.UnknownD = binary.LittleEndian.Uint32(data[17:21]) - - s.sessionID = resp.Session - s.key = resp.Key - s.maxLength = resp.MaxLength - s.compressed = (resp.Format & 0x01) != 0 - s.encoded = (resp.Format & 0x04) != 0 - - return nil -} - -// handleKeepAlive handles keep-alive packets -func (s *Stream) handleKeepAlive(data []byte) error { - // Send keep-alive response - return s.sendKeepAliveResponse() -} - -// handlePacket handles regular data packets -func (s *Stream) handlePacket(data []byte) error { - if len(data) < 4 { - return fmt.Errorf("packet too small") - } - - // Extract sequence number - sequence := binary.BigEndian.Uint16(data[2:4]) - - // Validate CRC - if !ValidateCRC(data, len(data), s.key) { - return fmt.Errorf("CRC validation failed") - } - - // Create protocol packet - packet := NewProtocolPacket(data[4:len(data)-2], -1) // Skip header and CRC - packet.Sequence = sequence - - // Handle sequence - if sequence == s.nextInSeq { - // In order packet - s.processInOrderPacket(packet) - s.nextInSeq++ - - // Check for any future packets that are now in order - s.processFuturePackets() - - // Send ACK - s.sendAck(sequence) - } else if sequence > s.nextInSeq { - // Future packet - store it - s.futurePackets[sequence] = packet - // Send out of order ACK - s.sendOutOfOrderAck(sequence) - } - // Ignore past packets (already processed) - - return nil -} - -// handleFragment handles fragmented packets -func (s *Stream) handleFragment(data []byte) error { - if len(data) < 8 { - return fmt.Errorf("fragment too small") - } - - sequence := binary.BigEndian.Uint16(data[2:4]) - totalSize := binary.BigEndian.Uint32(data[4:8]) - - // Validate CRC - if !ValidateCRC(data, len(data), s.key) { - return fmt.Errorf("CRC validation failed") - } - - // Initialize oversize buffer if needed - if s.oversizeBuffer == nil || s.oversizeLength != totalSize { - s.oversizeBuffer = make([]byte, totalSize) - s.oversizeOffset = 0 - s.oversizeLength = totalSize - } - - // Copy fragment data - fragmentData := data[8 : len(data)-2] // Skip header and CRC - copy(s.oversizeBuffer[s.oversizeOffset:], fragmentData) - s.oversizeOffset += uint32(len(fragmentData)) - - // Send ACK - s.sendAck(sequence) - s.nextInSeq = sequence + 1 - - // Check if we have the complete packet - if s.oversizeOffset >= s.oversizeLength { - // Process the complete packet - packet := NewProtocolPacket(s.oversizeBuffer[:s.oversizeLength], -1) - s.processInOrderPacket(packet) - - // Reset oversize buffer - s.oversizeBuffer = nil - s.oversizeOffset = 0 - s.oversizeLength = 0 - } - - return nil -} - -// handleAck handles acknowledgment packets -func (s *Stream) handleAck(data []byte) error { - if len(data) < 4 { - return fmt.Errorf("ack too small") - } - - sequence := binary.BigEndian.Uint16(data[2:4]) - - // Remove acknowledged packet from sent queue - if packet, ok := s.sentQueue[sequence]; ok { - packet.Acked = true - delete(s.sentQueue, sequence) - - // Update RTT based on this ACK - if packet.SentTime > 0 { - rtt := time.Since(time.Unix(int64(packet.SentTime), 0)) - s.updateRTT(rtt) - } - } - - s.lastAckReceived = sequence - - return nil -} - -// handleOutOfOrderAck handles out-of-order acknowledgments -func (s *Stream) handleOutOfOrderAck(data []byte) error { - if len(data) < 4 { - return fmt.Errorf("out of order ack too small") - } - - sequence := binary.BigEndian.Uint16(data[2:4]) - - // Resend packets between lastAckReceived and sequence - for seq := s.lastAckReceived + 1; seq < sequence; seq++ { - if packet, ok := s.sentQueue[seq]; ok { - s.resendPacket(packet) - } - } - - return nil -} - -// handleDisconnect handles disconnect packets -func (s *Stream) handleDisconnect(data []byte) error { - s.state = Disconnecting - - // Call disconnect callback - if s.onDisconnect != nil { - s.onDisconnect() - } - - return nil -} - -// handleCombined handles combined packets -func (s *Stream) handleCombined(data []byte) error { - if len(data) < 3 { - return fmt.Errorf("combined packet too small") - } - - offset := 2 // Skip opcode - - for offset < len(data)-2 { // Leave room for CRC - if offset+1 >= len(data) { - break - } - - // Read sub-packet length - length := uint16(data[offset]) - offset++ - - if length == 0xFF { - // Extended length - if offset+2 >= len(data) { - break - } - length = binary.BigEndian.Uint16(data[offset:]) - offset += 2 - } - - // Extract sub-packet - if offset+int(length) > len(data) { - break - } - - subPacket := data[offset : offset+int(length)] - offset += int(length) - - // Process sub-packet - s.Process(subPacket) - } - - return nil -} - -// handleAppCombined handles application-level combined packets -func (s *Stream) handleAppCombined(data []byte) error { - // Similar to handleCombined but for application packets - return s.handleCombined(data) -} - -// processInOrderPacket processes an in-order packet -func (s *Stream) processInOrderPacket(packet *ProtocolPacket) { - // Decompress if needed - if s.compressed && packet.EQ2Compressed { - decompressed := make([]byte, 8192) - size, err := Decompress(packet.Buffer, uint32(len(packet.Buffer)), decompressed, 8192) - if err == nil { - packet.Buffer = decompressed[:size] - packet.Size = size - } - } - - // Decode if needed - if s.encoded && packet.PacketEncrypted { - ChatDecode(packet.Buffer, len(packet.Buffer), int(s.key)) - packet.PacketEncrypted = false - } - - // Convert to application packet and call callback - if s.onPacket != nil { - appPacket := s.makeApplicationPacket(packet) - if appPacket != nil { - s.onPacket(appPacket) - } - } -} - -// processFuturePackets processes any future packets that are now in order -func (s *Stream) processFuturePackets() { - for { - if packet, ok := s.futurePackets[s.nextInSeq]; ok { - s.processInOrderPacket(packet) - delete(s.futurePackets, s.nextInSeq) - s.nextInSeq++ - } else { - break - } - } -} - -// makeApplicationPacket converts a protocol packet to an application packet -func (s *Stream) makeApplicationPacket(packet *ProtocolPacket) *ApplicationPacket { - if len(packet.Buffer) < int(s.opcodeSize) { - return nil - } - - var opcode uint16 - var data []byte - - if s.opcodeSize == 1 { - opcode = uint16(packet.Buffer[0]) - data = packet.Buffer[1:] - } else { - opcode = binary.LittleEndian.Uint16(packet.Buffer) - data = packet.Buffer[2:] - } - - appPacket := NewApplicationPacket(EmuOpcode(opcode), data) - appPacket.AppOpcodeSize = s.opcodeSize - - return appPacket -} - -// Send queues a packet for sending -func (s *Stream) Send(packet *ApplicationPacket) error { - s.mu.Lock() - defer s.mu.Unlock() - - // Convert to protocol packet - data := packet.Serialize() - protocolPacket := NewProtocolPacketWithOp(uint16(OPPacket), data) - - // Add to outgoing queue - s.outgoingQueue = append(s.outgoingQueue, protocolPacket) - - return nil -} - -// sendSessionResponse sends a session response -func (s *Stream) sendSessionResponse() error { - resp := &SessionResponse{ - Session: s.sessionID, - Key: s.key, - Format: 0, - MaxLength: s.maxLength, - } - - if s.compressed { - resp.Format |= 0x01 - } - if s.encoded { - resp.Format |= 0x04 - } - - // Build response packet - data := make([]byte, 21) - binary.BigEndian.PutUint16(data[0:2], uint16(OPSessionResponse)) - binary.LittleEndian.PutUint32(data[2:6], resp.Session) - binary.LittleEndian.PutUint32(data[6:10], resp.Key) - data[10] = resp.UnknownA - data[11] = resp.Format - data[12] = resp.UnknownB - binary.LittleEndian.PutUint32(data[13:17], resp.MaxLength) - binary.LittleEndian.PutUint32(data[17:21], resp.UnknownD) - - // Session response doesn't have CRC - return s.sendRaw(data) -} - -// sendKeepAliveResponse sends a keep-alive response -func (s *Stream) sendKeepAliveResponse() error { - data := make([]byte, 2) - binary.BigEndian.PutUint16(data, uint16(OPKeepAlive)) - return s.sendWithCRC(data) -} - -// sendAck sends an acknowledgment -func (s *Stream) sendAck(sequence uint16) error { - data := make([]byte, 4) - binary.BigEndian.PutUint16(data[0:2], uint16(OPAck)) - binary.BigEndian.PutUint16(data[2:4], sequence) - return s.sendWithCRC(data) -} - -// sendOutOfOrderAck sends an out-of-order acknowledgment -func (s *Stream) sendOutOfOrderAck(sequence uint16) error { - data := make([]byte, 4) - binary.BigEndian.PutUint16(data[0:2], uint16(OPOutOfOrderAck)) - binary.BigEndian.PutUint16(data[2:4], sequence) - return s.sendWithCRC(data) -} - -// sendWithCRC sends data with CRC -func (s *Stream) sendWithCRC(data []byte) error { - // Calculate CRC - crc := CalculateCRC16(data, s.key) - - // Append CRC - fullData := make([]byte, len(data)+2) - copy(fullData, data) - binary.BigEndian.PutUint16(fullData[len(data):], crc) - - return s.sendRaw(fullData) -} - -// sendRaw sends raw data (should be called from factory) -func (s *Stream) sendRaw(data []byte) error { - s.lastSendTime = time.Now() - s.sentPackets++ - // The actual sending will be done by StreamFactory - return nil -} - -// resendPacket resends a packet -func (s *Stream) resendPacket(packet *ProtocolPacket) { - packet.AttemptCount++ - packet.SentTime = int32(time.Now().Unix()) - // Re-queue for sending - s.outgoingQueue = append(s.outgoingQueue, packet) -} - -// updateRTT updates the round-trip time statistics -func (s *Stream) updateRTT(rtt time.Duration) { - if s.minRoundTrip == 0 || rtt < s.minRoundTrip { - s.minRoundTrip = rtt - } - if rtt > s.maxRoundTrip { - s.maxRoundTrip = rtt - } - - // Update average (simple moving average) - if s.avgRoundTrip == 0 { - s.avgRoundTrip = rtt - } else { - s.avgRoundTrip = (s.avgRoundTrip*3 + rtt) / 4 - } - - // Update retransmit timeout - s.retransmitTimeout = s.avgRoundTrip * 3 - if s.retransmitTimeout > 5*time.Second { - s.retransmitTimeout = 5 * time.Second - } -} - -// SetOnPacket sets the packet callback -func (s *Stream) SetOnPacket(callback func(*ApplicationPacket)) { - s.mu.Lock() - defer s.mu.Unlock() - s.onPacket = callback -} - -// SetOnDisconnect sets the disconnect callback -func (s *Stream) SetOnDisconnect(callback func()) { - s.mu.Lock() - defer s.mu.Unlock() - s.onDisconnect = callback -} \ No newline at end of file diff --git a/stream_factory.go b/stream_factory.go deleted file mode 100644 index 0284aa3..0000000 --- a/stream_factory.go +++ /dev/null @@ -1,471 +0,0 @@ -package eq2net - -import ( - "crypto/rand" - "encoding/binary" - "fmt" - "net" - "sync" - "time" -) - -// StreamFactory manages multiple UDP streams -type StreamFactory struct { - mu sync.RWMutex - - // Network - conn *net.UDPConn - listenAddr *net.UDPAddr - streamType StreamType - - // Streams - streams map[string]*Stream // Key is "IP:Port" - streamsByID map[uint32]*Stream // Key is session ID - - // Options - maxStreams int - readTimeout time.Duration - writeTimeout time.Duration - - // State - running bool - stopChan chan struct{} - - // Callbacks - onNewStream func(*Stream) - onStreamClosed func(*Stream) -} - -// NewStreamFactory creates a new stream factory -func NewStreamFactory(listenAddr string, streamType StreamType) (*StreamFactory, error) { - addr, err := net.ResolveUDPAddr("udp", listenAddr) - if err != nil { - return nil, err - } - - sf := &StreamFactory{ - listenAddr: addr, - streamType: streamType, - streams: make(map[string]*Stream), - streamsByID: make(map[uint32]*Stream), - maxStreams: 1000, - readTimeout: 30 * time.Second, - writeTimeout: 5 * time.Second, - stopChan: make(chan struct{}), - } - - return sf, nil -} - -// Start starts the stream factory -func (sf *StreamFactory) Start() error { - sf.mu.Lock() - defer sf.mu.Unlock() - - if sf.running { - return fmt.Errorf("stream factory already running") - } - - // Create UDP connection - conn, err := net.ListenUDP("udp", sf.listenAddr) - if err != nil { - return err - } - sf.conn = conn - - // Set buffer sizes - conn.SetReadBuffer(65536) - conn.SetWriteBuffer(65536) - - sf.running = true - - // Start worker goroutines - go sf.readLoop() - go sf.writeLoop() - go sf.maintenanceLoop() - - return nil -} - -// Stop stops the stream factory -func (sf *StreamFactory) Stop() { - sf.mu.Lock() - defer sf.mu.Unlock() - - if !sf.running { - return - } - - sf.running = false - close(sf.stopChan) - - if sf.conn != nil { - sf.conn.Close() - } - - // Close all streams - for _, stream := range sf.streams { - stream.SetState(Closed) - if stream.onDisconnect != nil { - stream.onDisconnect() - } - } - - sf.streams = make(map[string]*Stream) - sf.streamsByID = make(map[uint32]*Stream) -} - -// readLoop reads packets from the UDP connection -func (sf *StreamFactory) readLoop() { - buffer := make([]byte, 65536) - - for { - select { - case <-sf.stopChan: - return - default: - // Set read deadline - sf.conn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) - - // Read packet - n, remoteAddr, err := sf.conn.ReadFromUDP(buffer) - if err != nil { - // Check if it's a timeout (expected) - if netErr, ok := err.(net.Error); ok && netErr.Timeout() { - continue - } - // Actual error - continue - } - - // Process packet - data := make([]byte, n) - copy(data, buffer[:n]) - go sf.processPacket(data, remoteAddr) - } - } -} - -// writeLoop handles writing packets to streams -func (sf *StreamFactory) writeLoop() { - ticker := time.NewTicker(10 * time.Millisecond) - defer ticker.Stop() - - for { - select { - case <-sf.stopChan: - return - case <-ticker.C: - sf.processOutgoingPackets() - } - } -} - -// maintenanceLoop performs periodic maintenance -func (sf *StreamFactory) maintenanceLoop() { - ticker := time.NewTicker(1 * time.Second) - defer ticker.Stop() - - for { - select { - case <-sf.stopChan: - return - case <-ticker.C: - sf.performMaintenance() - } - } -} - -// processPacket processes an incoming packet -func (sf *StreamFactory) processPacket(data []byte, remoteAddr *net.UDPAddr) { - if len(data) < 2 { - return - } - - // Get or create stream - stream := sf.getOrCreateStream(remoteAddr, data) - if stream == nil { - return - } - - // Process packet in stream - err := stream.Process(data) - if err != nil { - // Handle error (log it, etc.) - } -} - -// getOrCreateStream gets an existing stream or creates a new one -func (sf *StreamFactory) getOrCreateStream(remoteAddr *net.UDPAddr, data []byte) *Stream { - sf.mu.Lock() - defer sf.mu.Unlock() - - key := fmt.Sprintf("%s:%d", remoteAddr.IP.String(), remoteAddr.Port) - - // Check if stream exists - if stream, ok := sf.streams[key]; ok { - return stream - } - - // Check if this is a session request - if len(data) >= 2 { - opcode := binary.BigEndian.Uint16(data) - if uint8(opcode&0xFF) == OPSessionRequest { - // Create new stream - stream := NewStream(remoteAddr, sf.streamType) - - // Generate session ID and key - sessionID := sf.generateSessionID() - sessionKey := sf.generateKey() - stream.SetSessionInfo(sessionID, sessionKey) - - // Add to maps - sf.streams[key] = stream - sf.streamsByID[sessionID] = stream - - // Call new stream callback - if sf.onNewStream != nil { - sf.onNewStream(stream) - } - - return stream - } - } - - return nil -} - -// processOutgoingPackets processes outgoing packets for all streams -func (sf *StreamFactory) processOutgoingPackets() { - sf.mu.RLock() - streams := make([]*Stream, 0, len(sf.streams)) - for _, stream := range sf.streams { - streams = append(streams, stream) - } - sf.mu.RUnlock() - - for _, stream := range streams { - sf.processStreamOutgoing(stream) - } -} - -// processStreamOutgoing processes outgoing packets for a stream -func (sf *StreamFactory) processStreamOutgoing(stream *Stream) { - stream.mu.Lock() - defer stream.mu.Unlock() - - // Process outgoing queue - for len(stream.outgoingQueue) > 0 { - packet := stream.outgoingQueue[0] - stream.outgoingQueue = stream.outgoingQueue[1:] - - // Assign sequence number - packet.Sequence = stream.nextOutSeq - stream.nextOutSeq++ - - // Build packet data - data := sf.buildPacketData(stream, packet) - - // Send packet - sf.sendPacket(stream.remoteAddr, data) - - // Add to sent queue for acknowledgment tracking - packet.SentTime = int32(time.Now().Unix()) - stream.sentQueue[packet.Sequence] = packet - } - - // Check for retransmissions - now := time.Now() - for seq, packet := range stream.sentQueue { - sentTime := time.Unix(int64(packet.SentTime), 0) - if now.Sub(sentTime) > stream.retransmitTimeout { - if packet.AttemptCount < 3 { - // Retransmit - packet.AttemptCount++ - packet.SentTime = int32(now.Unix()) - - data := sf.buildPacketData(stream, packet) - sf.sendPacket(stream.remoteAddr, data) - } else { - // Give up on this packet - delete(stream.sentQueue, seq) - } - } - } -} - -// buildPacketData builds the complete packet data including headers and CRC -func (sf *StreamFactory) buildPacketData(stream *Stream, packet *ProtocolPacket) []byte { - // Check if we need to fragment - maxDataSize := int(stream.maxLength) - 6 // Header (4) + CRC (2) - - if len(packet.Buffer) > maxDataSize { - // Need to fragment - return sf.buildFragmentData(stream, packet) - } - - // Build regular packet - data := make([]byte, len(packet.Buffer)+6) - - // Header - binary.BigEndian.PutUint16(data[0:2], uint16(OPPacket)) - binary.BigEndian.PutUint16(data[2:4], packet.Sequence) - - // Data - copy(data[4:], packet.Buffer) - - // CRC - crc := CalculateCRC16(data[:len(data)-2], stream.key) - binary.BigEndian.PutUint16(data[len(data)-2:], crc) - - return data -} - -// buildFragmentData builds a fragment packet -func (sf *StreamFactory) buildFragmentData(stream *Stream, packet *ProtocolPacket) []byte { - // For now, just truncate (proper fragmentation would split into multiple packets) - // This is a simplified version - real implementation would handle multiple fragments - - maxDataSize := int(stream.maxLength) - 10 // Header (8) + CRC (2) - dataSize := len(packet.Buffer) - if dataSize > maxDataSize { - dataSize = maxDataSize - } - - data := make([]byte, dataSize+10) - - // Header - binary.BigEndian.PutUint16(data[0:2], uint16(OPFragment)) - binary.BigEndian.PutUint16(data[2:4], packet.Sequence) - binary.BigEndian.PutUint32(data[4:8], uint32(len(packet.Buffer))) - - // Data - copy(data[8:], packet.Buffer[:dataSize]) - - // CRC - crc := CalculateCRC16(data[:len(data)-2], stream.key) - binary.BigEndian.PutUint16(data[len(data)-2:], crc) - - return data -} - -// sendPacket sends a packet -func (sf *StreamFactory) sendPacket(addr *net.UDPAddr, data []byte) error { - sf.conn.SetWriteDeadline(time.Now().Add(sf.writeTimeout)) - _, err := sf.conn.WriteToUDP(data, addr) - return err -} - -// performMaintenance performs periodic maintenance tasks -func (sf *StreamFactory) performMaintenance() { - sf.mu.Lock() - defer sf.mu.Unlock() - - now := time.Now() - toRemove := []string{} - - for key, stream := range sf.streams { - stream.mu.RLock() - lastReceive := stream.lastReceiveTime - state := stream.state - stream.mu.RUnlock() - - // Check for timeout - if now.Sub(lastReceive) > sf.readTimeout && state == Established { - stream.SetState(Disconnecting) - toRemove = append(toRemove, key) - } - - // Remove closed streams - if state == Closed || state == Disconnecting { - toRemove = append(toRemove, key) - } - } - - // Remove dead streams - for _, key := range toRemove { - if stream, ok := sf.streams[key]; ok { - delete(sf.streams, key) - delete(sf.streamsByID, stream.GetSessionID()) - - // Call closed callback - if sf.onStreamClosed != nil { - sf.onStreamClosed(stream) - } - } - } -} - -// generateSessionID generates a random session ID -func (sf *StreamFactory) generateSessionID() uint32 { - var id uint32 - for { - binary.Read(rand.Reader, binary.BigEndian, &id) - // Make sure it's not already in use - if _, exists := sf.streamsByID[id]; !exists && id != 0 { - return id - } - } -} - -// generateKey generates a random key -func (sf *StreamFactory) generateKey() uint32 { - var key uint32 - binary.Read(rand.Reader, binary.BigEndian, &key) - return key -} - -// GetStream gets a stream by remote address -func (sf *StreamFactory) GetStream(remoteAddr *net.UDPAddr) *Stream { - sf.mu.RLock() - defer sf.mu.RUnlock() - - key := fmt.Sprintf("%s:%d", remoteAddr.IP.String(), remoteAddr.Port) - return sf.streams[key] -} - -// GetStreamByID gets a stream by session ID -func (sf *StreamFactory) GetStreamByID(sessionID uint32) *Stream { - sf.mu.RLock() - defer sf.mu.RUnlock() - - return sf.streamsByID[sessionID] -} - -// GetStreamCount returns the number of active streams -func (sf *StreamFactory) GetStreamCount() int { - sf.mu.RLock() - defer sf.mu.RUnlock() - - return len(sf.streams) -} - -// SetOnNewStream sets the new stream callback -func (sf *StreamFactory) SetOnNewStream(callback func(*Stream)) { - sf.mu.Lock() - defer sf.mu.Unlock() - sf.onNewStream = callback -} - -// SetOnStreamClosed sets the stream closed callback -func (sf *StreamFactory) SetOnStreamClosed(callback func(*Stream)) { - sf.mu.Lock() - defer sf.mu.Unlock() - sf.onStreamClosed = callback -} - -// Broadcast sends a packet to all connected streams -func (sf *StreamFactory) Broadcast(packet *ApplicationPacket) { - sf.mu.RLock() - streams := make([]*Stream, 0, len(sf.streams)) - for _, stream := range sf.streams { - if stream.GetState() == Established { - streams = append(streams, stream) - } - } - sf.mu.RUnlock() - - for _, stream := range streams { - stream.Send(packet) - } -} \ No newline at end of file