clean /old/login/net
This commit is contained in:
parent
76fa77d590
commit
ffb75045a4
88
crc16.go
88
crc16.go
@ -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))
|
|
||||||
}
|
|
@ -4,278 +4,451 @@
|
|||||||
|
|
||||||
This file is part of EQ2Emulator.
|
This file is part of EQ2Emulator.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// Debug and common includes
|
||||||
#include "../common/debug.h"
|
#include "../common/debug.h"
|
||||||
|
|
||||||
|
// Standard library includes
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstring>
|
||||||
|
#include <ctime>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <format>
|
||||||
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <string.h>
|
#include <map>
|
||||||
#include <time.h>
|
#include <memory>
|
||||||
|
#include <random>
|
||||||
#include <signal.h>
|
#include <signal.h>
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <iostream>
|
#include <string_view>
|
||||||
|
#include <thread>
|
||||||
|
#include <termios.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
// Project common includes
|
||||||
#include "../common/queue.h"
|
#include "../common/queue.h"
|
||||||
#include "../common/timer.h"
|
#include "../common/timer.h"
|
||||||
|
|
||||||
#include "../common/seperator.h"
|
#include "../common/seperator.h"
|
||||||
|
|
||||||
#include "net.h"
|
|
||||||
#include "client.h"
|
|
||||||
|
|
||||||
#include "LoginDatabase.h"
|
|
||||||
#include "LWorld.h"
|
|
||||||
#include "../common/packet_functions.h"
|
#include "../common/packet_functions.h"
|
||||||
#include "../common/EQStreamFactory.h"
|
#include "../common/EQStreamFactory.h"
|
||||||
#include "../common/MiscFunctions.h"
|
#include "../common/MiscFunctions.h"
|
||||||
#include "../common/version.h"
|
#include "../common/version.h"
|
||||||
|
|
||||||
#include "../common/PacketStruct.h"
|
#include "../common/PacketStruct.h"
|
||||||
#include "../common/DataBuffer.h"
|
#include "../common/DataBuffer.h"
|
||||||
#include "../common/ConfigReader.h"
|
#include "../common/ConfigReader.h"
|
||||||
#include "../common/Log.h"
|
#include "../common/Log.h"
|
||||||
#include "../common/JsonParser.h"
|
#include "../common/JsonParser.h"
|
||||||
#include "../common/Common_Defines.h"
|
#include "../common/Common_Defines.h"
|
||||||
|
#include "../common/CRC16.h"
|
||||||
|
#include "../common/timer.h"
|
||||||
|
#include "../common/unix.h"
|
||||||
|
|
||||||
#ifdef WIN32
|
// Login server includes
|
||||||
#define snprintf _snprintf
|
#include "net.h"
|
||||||
#define vsnprintf _vsnprintf
|
#include "client.h"
|
||||||
#define strncasecmp _strnicmp
|
#include "LoginDatabase.h"
|
||||||
#define strcasecmp _stricmp
|
#include "LWorld.h"
|
||||||
#include <conio.h>
|
|
||||||
#else
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include "../common/unix.h"
|
|
||||||
#endif
|
|
||||||
EQStreamFactory eqsf(LoginStream);
|
|
||||||
map<int16,OpcodeManager*>EQOpcodeManager;
|
|
||||||
//TCPServer eqns(5999);
|
|
||||||
NetConnection net;
|
|
||||||
ClientList client_list;
|
|
||||||
LWorldList world_list;
|
|
||||||
LoginDatabase database;
|
|
||||||
ConfigReader configReader;
|
|
||||||
map<int16, int16> EQOpcodeVersions;
|
|
||||||
Timer statTimer(60000);
|
|
||||||
|
|
||||||
|
// Global instances
|
||||||
|
EQStreamFactory eqsf(LoginStream); // Factory for creating EQ network streams
|
||||||
|
std::map<std::int16_t, OpcodeManager*> 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<std::int16_t, std::int16_t> 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;
|
volatile bool RunLoops = true;
|
||||||
|
|
||||||
|
// Forward declarations
|
||||||
bool ReadLoginConfig();
|
bool ReadLoginConfig();
|
||||||
|
|
||||||
#ifdef PUBLICLOGIN
|
/**
|
||||||
char version[200], consoletitle[200];
|
* @brief Check if a key has been pressed (Linux implementation)
|
||||||
#endif
|
* @return true if a key is available, false otherwise
|
||||||
#include "../common/timer.h"
|
*/
|
||||||
|
int kbhit()
|
||||||
|
{
|
||||||
|
struct termios oldt, newt;
|
||||||
|
int ch;
|
||||||
|
int oldf;
|
||||||
|
|
||||||
#include "../common/CRC16.h"
|
// Get current terminal settings
|
||||||
#include <fstream>
|
tcgetattr(STDIN_FILENO, &oldt);
|
||||||
|
newt = oldt;
|
||||||
|
|
||||||
int main(int argc, char** argv){
|
// Disable canonical mode and echo
|
||||||
#ifdef _DEBUG
|
newt.c_lflag &= ~(ICANON | ECHO);
|
||||||
_CrtSetDbgFlag( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
|
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
||||||
#endif
|
|
||||||
if (signal(SIGINT, CatchSignal) == SIG_ERR) {
|
// Set non-blocking mode
|
||||||
cerr << "Could not set signal handler" << endl;
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
LogStart();
|
return 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();
|
LogParseConfigs();
|
||||||
|
|
||||||
|
// Display welcome header
|
||||||
net.WelcomeHeader();
|
net.WelcomeHeader();
|
||||||
|
|
||||||
srand(time(NULL));
|
// Initialize random number generator with current time
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 gen(rd());
|
||||||
|
|
||||||
if(!net.ReadLoginConfig())
|
// Read and validate login server configuration
|
||||||
return 1;
|
if (!net.ReadLoginConfig())
|
||||||
|
|
||||||
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]);
|
return 1;
|
||||||
if (configReader.processXML_Elements(structList[s]))
|
}
|
||||||
LogWrite(INIT__INFO, 0, "Init", "Loading Structs File %s completed..", structList[s]);
|
|
||||||
|
// 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<const char*, 2> 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
|
else
|
||||||
{
|
{
|
||||||
LogWrite(INIT__ERROR, 0, "Init", "Loading Structs File %s FAILED!", structList[s]);
|
LogWrite(INIT__ERROR, 0, "Init", "Loading Structs File %s FAILED!", structFile);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Initialize world server list
|
||||||
LogWrite(INIT__INFO, 0, "Init", "Initialize World List..");
|
LogWrite(INIT__INFO, 0, "Init", "Initialize World List..");
|
||||||
world_list.Init();
|
world_list.Init();
|
||||||
|
|
||||||
if(eqsf.listen_ip_address)
|
// Log listening configuration
|
||||||
LogWrite(INIT__INFO, 0, "Init", "Login server listening on %s port %i", eqsf.listen_ip_address, net.GetPort());
|
if (eqsf.listen_ip_address)
|
||||||
|
{
|
||||||
|
LogWrite(INIT__INFO, 0, "Init", "Login server listening on %s port %i",
|
||||||
|
eqsf.listen_ip_address, net.GetPort());
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
LogWrite(INIT__INFO, 0, "Init", "Login server listening on port %i", net.GetPort());
|
LogWrite(INIT__INFO, 0, "Init", "Login server listening on port %i", net.GetPort());
|
||||||
/*}
|
}
|
||||||
else {
|
|
||||||
cout << "EQNetworkServer.Open() error" << endl;
|
// Open network port for incoming connections
|
||||||
return 1;
|
if (!eqsf.Open(net.GetPort()))
|
||||||
}*/
|
{
|
||||||
if (!eqsf.Open(net.GetPort())) {
|
|
||||||
LogWrite(INIT__ERROR, 0, "Init", "Failed to open port %i.", net.GetPort());
|
LogWrite(INIT__ERROR, 0, "Init", "Failed to open port %i.", net.GetPort());
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set server running state
|
||||||
net.login_running = true;
|
net.login_running = true;
|
||||||
net.login_uptime = getCurrentTimestamp();
|
net.login_uptime = getCurrentTimestamp();
|
||||||
|
|
||||||
|
// Update console window title (no-op on Linux)
|
||||||
net.UpdateWindowTitle();
|
net.UpdateWindowTitle();
|
||||||
EQStream* eqs;
|
|
||||||
Timer* TimeoutTimer = new Timer(5000);
|
// Initialize timeout timer for connection management
|
||||||
TimeoutTimer->Start();
|
EQStream* eqs = nullptr;
|
||||||
while(RunLoops) {
|
auto timeoutTimer = std::make_unique<Timer>(5000);
|
||||||
|
timeoutTimer->Start();
|
||||||
|
|
||||||
|
// Main server loop
|
||||||
|
while (RunLoops)
|
||||||
|
{
|
||||||
|
// Update current time for all timers
|
||||||
Timer::SetCurrentTime();
|
Timer::SetCurrentTime();
|
||||||
while ((eqs = eqsf.Pop())) {
|
|
||||||
|
// Process new incoming connections
|
||||||
|
while ((eqs = eqsf.Pop()))
|
||||||
|
{
|
||||||
struct in_addr in;
|
struct in_addr in;
|
||||||
in.s_addr = eqs->GetRemoteIP();
|
in.s_addr = eqs->GetRemoteIP();
|
||||||
|
|
||||||
LogWrite(LOGIN__INFO, 0, "Login", "New client from IP: %s on port %i", inet_ntoa(in), ntohs(eqs->GetRemotePort()));
|
LogWrite(LOGIN__INFO, 0, "Login", "New client from IP: %s on port %i",
|
||||||
Client* client = new Client(eqs);
|
inet_ntoa(in), ntohs(eqs->GetRemotePort()));
|
||||||
|
|
||||||
|
// Create new client instance and add to client list
|
||||||
|
auto client = std::make_unique<Client>(eqs);
|
||||||
eqs->SetClientVersion(0);
|
eqs->SetClientVersion(0);
|
||||||
client_list.Add(client);
|
client_list.Add(client.release());
|
||||||
net.numclients++;
|
net.numclients++;
|
||||||
net.UpdateWindowTitle();
|
net.UpdateWindowTitle();
|
||||||
}
|
}
|
||||||
if(TimeoutTimer->Check()){
|
|
||||||
|
// Check for timed out connections
|
||||||
|
if (timeoutTimer->Check())
|
||||||
|
{
|
||||||
eqsf.CheckTimeout();
|
eqsf.CheckTimeout();
|
||||||
}
|
}
|
||||||
if(statTimer.Check()){
|
|
||||||
|
// Update statistics periodically
|
||||||
|
if (statTimer.Check())
|
||||||
|
{
|
||||||
world_list.UpdateWorldStats();
|
world_list.UpdateWorldStats();
|
||||||
database.RemoveOldWorldServerStats();
|
database.RemoveOldWorldServerStats();
|
||||||
database.FixBugReport();
|
database.FixBugReport();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process active clients and world servers
|
||||||
client_list.Process();
|
client_list.Process();
|
||||||
world_list.Process();
|
world_list.Process();
|
||||||
#ifdef WIN32
|
|
||||||
if(kbhit())
|
// Handle console input (Linux version)
|
||||||
|
if (kbhit())
|
||||||
{
|
{
|
||||||
int hitkey = getch();
|
int hitkey = getchar();
|
||||||
net.HitKey(hitkey);
|
|
||||||
}
|
switch (hitkey)
|
||||||
#endif
|
|
||||||
Sleep(1);
|
|
||||||
}
|
|
||||||
//close
|
|
||||||
//eqns.Close();
|
|
||||||
eqsf.Close();
|
|
||||||
world_list.Shutdown();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
#ifdef WIN32
|
|
||||||
void NetConnection::HitKey(int keyhit)
|
|
||||||
{
|
|
||||||
switch(keyhit)
|
|
||||||
{
|
{
|
||||||
case 'l':
|
case 'l':
|
||||||
case 'L': {
|
case 'L':
|
||||||
|
{
|
||||||
|
// List all connected world servers
|
||||||
world_list.ListWorldsToConsole();
|
world_list.ListWorldsToConsole();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'v':
|
case 'v':
|
||||||
case 'V':
|
case 'V':
|
||||||
{
|
{
|
||||||
printf("========Version Info=========\n");
|
// Display version information
|
||||||
printf("%s %s\n", EQ2EMU_MODULE, CURRENT_VERSION);
|
std::cout << "========Version Info=========\n";
|
||||||
printf("Last Compiled on %s %s\n", COMPILE_DATE, COMPILE_TIME);
|
std::cout << std::format("{} {}\n", EQ2EMU_MODULE, CURRENT_VERSION);
|
||||||
printf("=============================\n\n");
|
std::cout << std::format("Last Compiled on {} {}\n", COMPILE_DATE, COMPILE_TIME);
|
||||||
|
std::cout << "=============================\n\n";
|
||||||
break;
|
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) {
|
case 'H':
|
||||||
cout << "Got signal " << sig_num << endl;
|
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;
|
RunLoops = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NetConnection::ReadLoginConfig() {
|
/**
|
||||||
|
* @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);
|
JsonParser parser(MAIN_CONFIG_FILE);
|
||||||
if(!parser.IsLoaded()) {
|
if (!parser.IsLoaded())
|
||||||
|
{
|
||||||
LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE);
|
LogWrite(INIT__ERROR, 0, "Init", "Failed to find %s in server directory..", MAIN_CONFIG_FILE);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse server port configuration
|
||||||
std::string serverport = parser.getValue("loginconfig.serverport");
|
std::string serverport = parser.getValue("loginconfig.serverport");
|
||||||
std::string serverip = parser.getValue("loginconfig.serverip");
|
std::string serverip = parser.getValue("loginconfig.serverip");
|
||||||
|
|
||||||
if (!parser.convertStringToUnsignedShort(serverport, port)) {
|
if (!parser.convertStringToUnsignedShort(serverport, port_))
|
||||||
|
{
|
||||||
LogWrite(INIT__ERROR, 0, "Init", "Failed to translate loginconfig.serverport..");
|
LogWrite(INIT__ERROR, 0, "Init", "Failed to translate loginconfig.serverport..");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(serverip.size() > 0) {
|
// Set listening IP address if specified
|
||||||
|
if (!serverip.empty())
|
||||||
|
{
|
||||||
eqsf.listen_ip_address = new char[serverip.size() + 1];
|
eqsf.listen_ip_address = new char[serverip.size() + 1];
|
||||||
strcpy(eqsf.listen_ip_address, serverip.c_str());
|
std::strcpy(eqsf.listen_ip_address, serverip.c_str());
|
||||||
}
|
}
|
||||||
else {
|
else
|
||||||
|
{
|
||||||
safe_delete(eqsf.listen_ip_address);
|
safe_delete(eqsf.listen_ip_address);
|
||||||
eqsf.listen_ip_address = nullptr;
|
eqsf.listen_ip_address = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse account creation setting
|
||||||
std::string acctcreate_str = parser.getValue("loginconfig.accountcreation");
|
std::string acctcreate_str = parser.getValue("loginconfig.accountcreation");
|
||||||
int16 allow_acct = 0;
|
std::uint16_t allow_acct = 0;
|
||||||
parser.convertStringToUnsignedShort(acctcreate_str, allow_acct);
|
parser.convertStringToUnsignedShort(acctcreate_str, allow_acct);
|
||||||
allowAccountCreation = allow_acct > 0 ? true : false;
|
allow_account_creation_ = (allow_acct > 0);
|
||||||
|
|
||||||
|
// Parse expansion and feature flags
|
||||||
std::string expflag_str = parser.getValue("loginconfig.expansionflag");
|
std::string expflag_str = parser.getValue("loginconfig.expansionflag");
|
||||||
parser.convertStringToUnsignedInt(expflag_str, expansionFlag);
|
parser.convertStringToUnsignedInt(expflag_str, expansion_flag_);
|
||||||
|
|
||||||
std::string citiesflag_str = parser.getValue("loginconfig.citiesflag");
|
std::string citiesflag_str = parser.getValue("loginconfig.citiesflag");
|
||||||
parser.convertStringToUnsignedChar(citiesflag_str, citiesFlag);
|
parser.convertStringToUnsignedChar(citiesflag_str, cities_flag_);
|
||||||
|
|
||||||
std::string defaultsublevel_str = parser.getValue("loginconfig.defaultsubscriptionlevel");
|
std::string defaultsublevel_str = parser.getValue("loginconfig.defaultsubscriptionlevel");
|
||||||
parser.convertStringToUnsignedInt(defaultsublevel_str, defaultSubscriptionLevel);
|
parser.convertStringToUnsignedInt(defaultsublevel_str, default_subscription_level_);
|
||||||
|
|
||||||
std::string enableraces_str = parser.getValue("loginconfig.enabledraces");
|
std::string enableraces_str = parser.getValue("loginconfig.enabledraces");
|
||||||
parser.convertStringToUnsignedInt(enableraces_str, enabledRaces);
|
parser.convertStringToUnsignedInt(enableraces_str, enabled_races_);
|
||||||
|
|
||||||
web_loginaddress = parser.getValue("loginconfig.webloginaddress");
|
// Parse web server configuration
|
||||||
web_certfile = parser.getValue("loginconfig.webcertfile");
|
web_login_address_ = parser.getValue("loginconfig.webloginaddress");
|
||||||
web_keyfile = parser.getValue("loginconfig.webkeyfile");
|
web_cert_file_ = parser.getValue("loginconfig.webcertfile");
|
||||||
web_keypassword = parser.getValue("loginconfig.webkeypassword");
|
web_key_file_ = parser.getValue("loginconfig.webkeyfile");
|
||||||
web_hardcodeuser = parser.getValue("loginconfig.webhardcodeuser");
|
web_key_password_ = parser.getValue("loginconfig.webkeypassword");
|
||||||
web_hardcodepassword = parser.getValue("loginconfig.webhardcodepassword");
|
web_hardcode_user_ = parser.getValue("loginconfig.webhardcodeuser");
|
||||||
|
web_hardcode_password_ = parser.getValue("loginconfig.webhardcodepassword");
|
||||||
|
|
||||||
std::string webloginport_str = parser.getValue("loginconfig.webloginport");
|
std::string webloginport_str = parser.getValue("loginconfig.webloginport");
|
||||||
parser.convertStringToUnsignedShort(webloginport_str, web_loginport);
|
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..");
|
LogWrite(INIT__INFO, 0, "Init", "Database init begin..");
|
||||||
//remove this when all database calls are using the new database class
|
if (!database.Init())
|
||||||
if (!database.Init()) {
|
{
|
||||||
LogWrite(INIT__ERROR, 0, "Init", "Database init FAILED!");
|
LogWrite(INIT__ERROR, 0, "Init", "Database init FAILED!");
|
||||||
LogStop();
|
LogStop();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load opcode definitions from database
|
||||||
LogWrite(INIT__INFO, 0, "Init", "Loading opcodes 2.0..");
|
LogWrite(INIT__INFO, 0, "Init", "Loading opcodes 2.0..");
|
||||||
EQOpcodeVersions = database.GetVersions();
|
EQOpcodeVersions = database.GetVersions();
|
||||||
map<int16,int16>::iterator version_itr2;
|
|
||||||
int16 version1 = 0;
|
for (const auto& [version, _] : EQOpcodeVersions)
|
||||||
for (version_itr2 = EQOpcodeVersions.begin(); version_itr2 != EQOpcodeVersions.end(); version_itr2++) {
|
{
|
||||||
version1 = version_itr2->first;
|
EQOpcodeManager[version] = new RegularOpcodeManager();
|
||||||
EQOpcodeManager[version1] = new RegularOpcodeManager();
|
auto opcodes = database.GetOpcodes(version);
|
||||||
map<string, uint16> eq = database.GetOpcodes(version1);
|
|
||||||
if(!EQOpcodeManager[version1]->LoadOpcodes(&eq)) {
|
if (!EQOpcodeManager[version]->LoadOpcodes(&opcodes))
|
||||||
|
{
|
||||||
LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!");
|
LogWrite(INIT__ERROR, 0, "Init", "Loading opcodes failed. Make sure you have sourced the opcodes.sql file!");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -284,80 +457,121 @@ bool NetConnection::ReadLoginConfig() {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void NetConnection::UpdateWindowTitle(char* iNewTitle) {
|
/**
|
||||||
#ifdef WIN32
|
* @brief Updates the console window title with server statistics
|
||||||
char tmp[500];
|
* @param iNewTitle Optional custom title string
|
||||||
if (iNewTitle) {
|
* Note: This is a no-op on Linux, kept for API compatibility
|
||||||
snprintf(tmp, sizeof(tmp), "Login: %s", iNewTitle);
|
*/
|
||||||
}
|
void NetConnection::UpdateWindowTitle(const char* iNewTitle)
|
||||||
else {
|
{
|
||||||
snprintf(tmp, sizeof(tmp), "%s, Version: %s: %i Server(s), %i Client(s) Connected", EQ2EMU_MODULE, CURRENT_VERSION, net.numservers, net.numclients);
|
// No-op on Linux - terminal titles are handled differently
|
||||||
}
|
// Could potentially use ANSI escape codes if needed:
|
||||||
SetConsoleTitle(tmp);
|
// std::cout << "\033]0;" << title << "\007";
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Displays the welcome header with ASCII art and information
|
||||||
|
*/
|
||||||
void NetConnection::WelcomeHeader()
|
void NetConnection::WelcomeHeader()
|
||||||
{
|
{
|
||||||
#ifdef _WIN32
|
// Use ANSI color codes for Linux terminal
|
||||||
HANDLE console = GetStdHandle(STD_OUTPUT_HANDLE);
|
const char* WHITE_BOLD = "\033[1;37m";
|
||||||
SetConsoleTextAttribute(console, FOREGROUND_WHITE_BOLD);
|
const char* YELLOW_BOLD = "\033[1;33m";
|
||||||
#endif
|
const char* GREEN_BOLD = "\033[1;32m";
|
||||||
printf("Module: %s, Version: %s", EQ2EMU_MODULE, CURRENT_VERSION);
|
const char* MAGENTA_BOLD = "\033[1;35m";
|
||||||
#ifdef _WIN32
|
const char* RESET = "\033[0m";
|
||||||
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
|
|
||||||
|
|
||||||
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) {
|
* @brief Initializes the web server for HTTP/HTTPS API access
|
||||||
try {
|
* @param web_ipaddr IP address to bind to
|
||||||
login_webserver = new WebServer(web_ipaddr, web_port, cert_file, key_file, key_password, hardcode_user, hardcode_password);
|
* @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<WebServer>(
|
||||||
|
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)
|
||||||
|
);
|
||||||
|
|
||||||
login_webserver->register_route("/status", NetConnection::Web_loginhandle_status);
|
// Register API routes
|
||||||
login_webserver->register_route("/worlds", NetConnection::Web_loginhandle_worlds);
|
login_webserver_->register_route("/status", NetConnection::Web_loginhandle_status);
|
||||||
login_webserver->run();
|
login_webserver_->register_route("/worlds", NetConnection::Web_loginhandle_worlds);
|
||||||
LogWrite(INIT__INFO, 0, "Init", "Login Web Server is listening on %s:%u..", web_ipaddr.c_str(), web_port);
|
|
||||||
|
// 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) {
|
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());
|
{
|
||||||
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
288
old/login/net.h
288
old/login/net.h
@ -4,144 +4,208 @@
|
|||||||
|
|
||||||
This file is part of EQ2Emulator.
|
This file is part of EQ2Emulator.
|
||||||
*/
|
*/
|
||||||
#ifdef WIN32
|
|
||||||
#define WIN32_LEAN_AND_MEAN
|
|
||||||
#include <windows.h>
|
|
||||||
#include <winsock.h>
|
|
||||||
#else
|
|
||||||
#include <sys/socket.h>
|
|
||||||
#include <netinet/in.h>
|
|
||||||
#include <arpa/inet.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//#include <netdb.h>
|
#pragma once
|
||||||
#include <errno.h>
|
|
||||||
#include <fcntl.h>
|
// Linux network headers
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
// Standard library includes
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// Project includes
|
||||||
#include "../common/types.h"
|
#include "../common/types.h"
|
||||||
#include "../common/Web/WebServer.h"
|
#include "../common/Web/WebServer.h"
|
||||||
#include "../common/MiscFunctions.h"
|
#include "../common/MiscFunctions.h"
|
||||||
|
|
||||||
void CatchSignal(int sig_num);
|
// Forward declarations
|
||||||
enum eServerMode { Standalone, Master, Slave, Mesh };
|
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
|
class NetConnection
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
NetConnection() {
|
/**
|
||||||
port = 5999;
|
* @brief Default constructor - initializes all network settings to defaults
|
||||||
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;
|
NetConnection() noexcept;
|
||||||
|
|
||||||
// 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
|
* @brief Destructor - ensures proper cleanup of network resources
|
||||||
// sub_level = 2 membership, you can 'create characters on time locked servers' vs standard
|
*/
|
||||||
// sub_level = 0 forces popup on close to web browser
|
~NetConnection();
|
||||||
defaultSubscriptionLevel = 0xFFFFFFFF;
|
|
||||||
|
|
||||||
// disable extra races FAE(16) ARASAI (17) SARNAK (18) -- with 4096/8192 flags, no visibility of portraits
|
// Disable copy operations (network connection should be unique)
|
||||||
enabledRaces = 0xFFFF; // 0xCFFF
|
NetConnection(const NetConnection&) = delete;
|
||||||
|
NetConnection& operator=(const NetConnection&) = delete;
|
||||||
|
|
||||||
web_loginport = 0;
|
// Enable move operations for potential future use
|
||||||
|
NetConnection(NetConnection&&) = default;
|
||||||
|
NetConnection& operator=(NetConnection&&) = default;
|
||||||
|
|
||||||
login_webserver = nullptr;
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
login_running = false;
|
/**
|
||||||
login_uptime = getCurrentTimestamp();
|
* @brief Initializes the network connection
|
||||||
}
|
* @return true if initialization successful, false otherwise
|
||||||
|
*/
|
||||||
~NetConnection() {
|
|
||||||
safe_delete(login_webserver);
|
|
||||||
}
|
|
||||||
|
|
||||||
void UpdateWindowTitle(char* iNewTitle = 0);
|
|
||||||
bool Init();
|
bool Init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Listens for new client connections
|
||||||
|
*/
|
||||||
void ListenNewClients();
|
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; }
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Reads configuration from the login config file
|
||||||
|
* @return true if config loaded successfully, false otherwise
|
||||||
|
*/
|
||||||
bool ReadLoginConfig();
|
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; }
|
* @brief Displays the welcome header with version and copyright info
|
||||||
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 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);
|
/**
|
||||||
|
* @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 void Web_loginhandle_status(const http::request<http::string_body>& req, http::response<http::string_body>& res);
|
// Static web server request handlers
|
||||||
static void Web_loginhandle_worlds(const http::request<http::string_body>& req, http::response<http::string_body>& res);
|
/**
|
||||||
|
* @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<http::string_body>& req,
|
||||||
|
http::response<http::string_body>& 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<http::string_body>& req,
|
||||||
|
http::response<http::string_body>& 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<std::int32_t> numclients{0}; // Current number of connected clients
|
||||||
|
std::atomic<std::int32_t> numservers{0}; // Current number of connected world servers
|
||||||
|
std::atomic<bool> login_running{false}; // Flag indicating if login server is running
|
||||||
|
std::atomic<std::int64_t> login_uptime{0}; // Timestamp when server started
|
||||||
|
|
||||||
bool login_running;
|
|
||||||
std::atomic<int64> login_uptime;
|
|
||||||
protected:
|
protected:
|
||||||
friend class LWorld;
|
friend class LWorld; // Allow LWorld access to protected members
|
||||||
bool Uplink_WrongVersion;
|
|
||||||
|
/**
|
||||||
|
* @brief Flag indicating uplink version mismatch
|
||||||
|
*/
|
||||||
|
bool uplink_wrong_version_{false};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int16 port;
|
// Network configuration
|
||||||
int listening_socket;
|
std::uint16_t port_{5999}; // Server listening port
|
||||||
char masteraddress[300];
|
int listening_socket_{0}; // Main listening socket descriptor
|
||||||
int16 uplinkport;
|
|
||||||
char uplinkaccount[300];
|
// Master server configuration (for non-standalone modes)
|
||||||
char uplinkpassword[300];
|
std::array<char, 300> master_address_{}; // Master server address
|
||||||
eServerMode LoginMode;
|
std::uint16_t uplink_port_{0}; // Port for uplink connection
|
||||||
bool allowAccountCreation;
|
std::array<char, 300> uplink_account_{}; // Account for uplink authentication
|
||||||
int32 expansionFlag;
|
std::array<char, 300> uplink_password_{}; // Password for uplink authentication
|
||||||
int8 citiesFlag;
|
ServerMode login_mode_{ServerMode::Standalone}; // Current server operation mode
|
||||||
int32 defaultSubscriptionLevel;
|
|
||||||
int32 enabledRaces;
|
// Account and expansion settings
|
||||||
std::string web_loginaddress;
|
bool allow_account_creation_{true}; // Allow new account creation
|
||||||
std::string web_certfile;
|
std::uint32_t expansion_flag_{0x7CFF}; // Enabled expansions bitfield
|
||||||
std::string web_keyfile;
|
std::uint8_t cities_flag_{0xFF}; // Enabled starting cities bitfield
|
||||||
std::string web_keypassword;
|
std::uint32_t default_subscription_level_{0xFFFFFFFF}; // Default subscription level for new accounts
|
||||||
std::string web_hardcodeuser;
|
std::uint32_t enabled_races_{0xFFFF}; // Enabled races bitfield
|
||||||
std::string web_hardcodepassword;
|
|
||||||
int16 web_loginport;
|
// Web server configuration
|
||||||
WebServer* login_webserver;
|
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<WebServer> login_webserver_; // Web server instance
|
||||||
};
|
};
|
56
opcodes.go
56
opcodes.go
@ -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
|
|
||||||
)
|
|
356
packet.go
356
packet.go
@ -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
|
|
||||||
}
|
|
675
stream.go
675
stream.go
@ -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
|
|
||||||
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user