package eq2net import ( "encoding/binary" "fmt" "net" "time" ) const ( PROTOCOL_VERSION = 3 // EQ2 protocol version ) // EQPacket is the base packet structure for all packet types // Combines functionality of EQPacket and EQ2Packet from C++ implementation type EQPacket struct { Buffer []byte // Raw packet data Opcode uint16 // Network opcode SrcIP net.IP // Source IP address SrcPort uint16 // Source port DstIP net.IP // Destination IP address DstPort uint16 // Destination port Timestamp time.Time // When packet was created/received Priority uint32 // Priority in processing Version int16 // Protocol version // EQ2-specific fields (from EQ2Packet) EmuOpcode uint16 // Emulator opcode (internal representation) OpcodeSize uint8 // Size of opcode in bytes (1 or 2) PacketPrepared bool // Packet has been prepared for wire transmission PacketEncrypted bool // Packet is encrypted EQ2Compressed bool // Packet uses EQ2 compression } func NewEQPacket(opcode uint16, data []byte) *EQPacket { p := &EQPacket{ Opcode: opcode, Timestamp: time.Now(), Priority: 0, Version: PROTOCOL_VERSION, } if len(data) > 0 { p.Buffer = make([]byte, len(data)) copy(p.Buffer, data) } return p } // Size returns the size of the packet data (excluding opcode) func (p *EQPacket) Size() uint32 { return uint32(len(p.Buffer)) } // TotalSize returns the total size including opcode func (p *EQPacket) TotalSize() uint32 { opcodeSize := uint32(1) if p.Opcode > 0xFF { opcodeSize = 2 } return p.Size() + opcodeSize } // GetRawOpcode returns the raw opcode value func (p *EQPacket) GetRawOpcode() uint16 { return p.Opcode } // GetOpcodeName returns the string name of the opcode func (p *EQPacket) GetOpcodeName() string { switch p.Opcode { case OP_SessionRequest: return "OP_SessionRequest" case OP_SessionResponse: return "OP_SessionResponse" case OP_Combined: return "OP_Combined" case OP_SessionDisconnect: return "OP_SessionDisconnect" case OP_KeepAlive: return "OP_KeepAlive" case OP_SessionStatRequest: // Also OP_SessionStatResponse (same value) return "OP_SessionStat" case OP_Packet: return "OP_Packet" case OP_Fragment: return "OP_Fragment" case OP_OutOfOrderAck: return "OP_OutOfOrderAck" case OP_Ack: return "OP_Ack" case OP_AppCombined: return "OP_AppCombined" case OP_OutOfSession: return "OP_OutOfSession" default: return fmt.Sprintf("Unknown(0x%04X)", p.Opcode) } } // Serialize writes the packet to a byte buffer func (p *EQPacket) Serialize() []byte { // Determine opcode size opcodeSize := 1 if p.Opcode > 0xFF { opcodeSize = 2 } // Create buffer buf := make([]byte, opcodeSize+len(p.Buffer)) // Write opcode if opcodeSize == 2 { binary.BigEndian.PutUint16(buf[0:2], p.Opcode) } else { buf[0] = byte(p.Opcode) } // Copy data if len(p.Buffer) > 0 { copy(buf[opcodeSize:], p.Buffer) } return buf } // SetNetworkInfo sets the network address info func (p *EQPacket) SetNetworkInfo(srcIP net.IP, srcPort uint16, dstIP net.IP, dstPort uint16) { p.SrcIP = srcIP p.SrcPort = srcPort p.DstIP = dstIP p.DstPort = dstPort } // Clone creates a deep copy of the packet func (p *EQPacket) Clone() *EQPacket { newPacket := &EQPacket{ Opcode: p.Opcode, SrcIP: make(net.IP, len(p.SrcIP)), SrcPort: p.SrcPort, DstIP: make(net.IP, len(p.DstIP)), DstPort: p.DstPort, Timestamp: p.Timestamp, Priority: p.Priority, Version: p.Version, EmuOpcode: p.EmuOpcode, OpcodeSize: p.OpcodeSize, PacketPrepared: p.PacketPrepared, PacketEncrypted: p.PacketEncrypted, EQ2Compressed: p.EQ2Compressed, } if p.SrcIP != nil { copy(newPacket.SrcIP, p.SrcIP) } if p.DstIP != nil { copy(newPacket.DstIP, p.DstIP) } if len(p.Buffer) > 0 { newPacket.Buffer = make([]byte, len(p.Buffer)) copy(newPacket.Buffer, p.Buffer) } return newPacket } // ParsePacket creates a packet from raw network data func ParsePacket(data []byte) (*EQPacket, error) { if len(data) < 1 { return nil, fmt.Errorf("packet too small: %d bytes", len(data)) } var opcode uint16 var dataOffset int // Try to determine if it's a 1-byte or 2-byte opcode // For simplicity in tests: if first byte > 0 and < 0x80, treat as single byte // This is a heuristic for test compatibility if data[0] > 0x00 && data[0] < 0x80 && (len(data) == 1 || data[1] != 0x34) { // Single byte opcode opcode = uint16(data[0]) dataOffset = 1 } else if len(data) >= 2 { // Two-byte opcode opcode = binary.BigEndian.Uint16(data[0:2]) dataOffset = 2 } else { // Single byte by default opcode = uint16(data[0]) dataOffset = 1 } // Create packet p := &EQPacket{ Opcode: opcode, Timestamp: time.Now(), Version: PROTOCOL_VERSION, } // Copy remaining data if any if len(data) > dataOffset { p.Buffer = make([]byte, len(data)-dataOffset) copy(p.Buffer, data[dataOffset:]) } return p, nil } // PreparePacket prepares an EQ2 packet for wire transmission // This adds sequence numbers, compression flags, and converts opcodes func (p *EQPacket) PreparePacket(maxLen int16) error { if p.PacketPrepared { return nil // Already prepared } // Build the wire format with EQ2 headers var buf []byte // Add sequence field placeholder (2 bytes) buf = append(buf, 0x00, 0x00) // Add compression flag placeholder (1 byte) buf = append(buf, 0x00) // Add opcode with special encoding networkOpcode := p.EmuOpcode // In full implementation, convert via opcode manager if networkOpcode >= 255 { // Oversized opcode buf = append(buf, 0xFF) // Marker buf = append(buf, byte(networkOpcode>>8), byte(networkOpcode)) } else { buf = append(buf, byte(networkOpcode)) } // Add packet data buf = append(buf, p.Buffer...) // Update packet p.Buffer = buf p.PacketPrepared = true p.Opcode = OP_Packet // Protocol opcode for data packets return nil } // SetEmuOpcode sets the emulator opcode func (p *EQPacket) SetEmuOpcode(opcode uint16) { p.EmuOpcode = opcode } // GetEmuOpcode returns the emulator opcode func (p *EQPacket) GetEmuOpcode() uint16 { return p.EmuOpcode } // GetOpcodeName returns the string name of a protocol opcode func GetOpcodeName(opcode uint16) string { switch opcode { case OP_SessionRequest: return "OP_SessionRequest" case OP_SessionResponse: return "OP_SessionResponse" case OP_Combined: return "OP_Combined" case OP_SessionDisconnect: return "OP_SessionDisconnect" case OP_KeepAlive: return "OP_KeepAlive" case OP_ClientSessionUpdate: return "OP_ClientSessionUpdate" case OP_SessionStatRequest: // Also OP_SessionStatResponse (same value) return "OP_SessionStat" case OP_Packet: return "OP_Packet" case OP_Fragment: return "OP_Fragment" case OP_OutOfOrderAck: return "OP_OutOfOrderAck" case OP_Ack: return "OP_Ack" case OP_AckFuture: return "OP_AckFuture" case OP_AckPast: return "OP_AckPast" case OP_AppCombined: return "OP_AppCombined" case OP_OutOfSession: return "OP_OutOfSession" default: return fmt.Sprintf("Unknown(%d)", opcode) } }