// Package eq2net implements the EverQuest 2 network protocol package eq2net import ( "encoding/binary" "fmt" "net" "time" ) // Protocol opcodes for low-level packet control const ( OPSessionRequest = 0x0001 OPSessionResponse = 0x0002 OPCombined = 0x0003 OPSessionDisconnect = 0x0005 OPKeepAlive = 0x0006 OPSessionStatRequest = 0x0007 OPSessionStatResponse = 0x0008 OPPacket = 0x0009 OPFragment = 0x000D OPOutOfOrderAck = 0x0011 OPAck = 0x0015 OPAppCombined = 0x0019 OPOutOfSession = 0x001D ) // EQPacket is the base packet type for all EverQuest packets type EQPacket struct { // Core packet data Buffer []byte Size uint32 Opcode uint16 // Network information SrcIP net.IP DstIP net.IP SrcPort uint16 DstPort uint16 // Metadata Priority uint32 Timestamp time.Time Version int16 } // NewEQPacket creates a new packet with the specified opcode and data func NewEQPacket(opcode uint16, data []byte) *EQPacket { p := &EQPacket{ Opcode: opcode, Timestamp: time.Now(), } if len(data) > 0 { p.Buffer = make([]byte, len(data)) copy(p.Buffer, data) p.Size = uint32(len(data)) } return p } // TotalSize returns the total packet size including opcode func (p *EQPacket) TotalSize() uint32 { return p.Size + 2 // +2 for opcode } // SetNetworkInfo sets the source and destination network information 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 } // CopyInfo copies network and timing information from another packet func (p *EQPacket) CopyInfo(other *EQPacket) { p.SrcIP = other.SrcIP p.SrcPort = other.SrcPort p.DstIP = other.DstIP p.DstPort = other.DstPort p.Timestamp = other.Timestamp p.Version = other.Version } // EQProtocolPacket handles low-level protocol operations type EQProtocolPacket struct { *EQPacket // Protocol state flags Compressed bool Prepared bool Encrypted bool Acked bool // Reliability tracking SentTime time.Time AttemptCount uint8 Sequence uint32 } // NewEQProtocolPacket creates a new protocol packet func NewEQProtocolPacket(opcode uint16, data []byte) *EQProtocolPacket { return &EQProtocolPacket{ EQPacket: NewEQPacket(opcode, data), } } // NewEQProtocolPacketFromBuffer creates a protocol packet from raw buffer func NewEQProtocolPacketFromBuffer(buffer []byte, opcodeOverride int) (*EQProtocolPacket, error) { if len(buffer) < 2 { return nil, fmt.Errorf("buffer too small for opcode") } var opcode uint16 var dataOffset int if opcodeOverride >= 0 { opcode = uint16(opcodeOverride) dataOffset = 0 } else { opcode = binary.BigEndian.Uint16(buffer[:2]) dataOffset = 2 } var data []byte if len(buffer) > dataOffset { data = buffer[dataOffset:] } return NewEQProtocolPacket(opcode, data), nil } // Serialize writes the protocol packet to a byte buffer func (p *EQProtocolPacket) Serialize(offset int) []byte { // Allocate buffer for opcode + data result := make([]byte, 2+len(p.Buffer)-offset) // Write opcode (big-endian) if p.Opcode > 0xFF { binary.BigEndian.PutUint16(result[0:2], p.Opcode) } else { result[0] = 0 result[1] = byte(p.Opcode) } // Copy packet data if len(p.Buffer) > offset { copy(result[2:], p.Buffer[offset:]) } return result } // IsProtocolPacket checks if the opcode is a valid protocol packet func IsProtocolPacket(opcode uint16) bool { switch opcode { case OPSessionRequest, OPSessionDisconnect, OPKeepAlive, OPSessionStatResponse, OPPacket, OPCombined, OPFragment, OPAck, OPOutOfOrderAck, OPOutOfSession: return true default: return false } } // Copy creates a deep copy of the protocol packet func (p *EQProtocolPacket) Copy() *EQProtocolPacket { newPacket := &EQProtocolPacket{ EQPacket: NewEQPacket(p.Opcode, p.Buffer), Compressed: p.Compressed, Prepared: p.Prepared, Encrypted: p.Encrypted, Acked: p.Acked, SentTime: p.SentTime, AttemptCount: p.AttemptCount, Sequence: p.Sequence, } newPacket.CopyInfo(p.EQPacket) return newPacket } // EQApplicationPacket represents high-level application packets type EQApplicationPacket struct { *EQPacket // Cached emulator opcode EmuOpcode uint16 // Opcode size (1 or 2 bytes) OpcodeSize uint8 } // DefaultOpcodeSize is the default size for application opcodes var DefaultOpcodeSize uint8 = 2 // NewEQApplicationPacket creates a new application packet func NewEQApplicationPacket(opcode uint16, data []byte) *EQApplicationPacket { return &EQApplicationPacket{ EQPacket: NewEQPacket(opcode, data), EmuOpcode: opcode, OpcodeSize: DefaultOpcodeSize, } } // Serialize writes the application packet to a byte buffer func (p *EQApplicationPacket) Serialize() []byte { opcodeBytes := p.OpcodeSize // Special handling for opcodes with low byte = 0x00 if p.OpcodeSize == 2 && (p.Opcode&0x00FF) == 0 { opcodeBytes = 3 } result := make([]byte, uint32(opcodeBytes)+p.Size) if p.OpcodeSize == 1 { result[0] = byte(p.Opcode) } else { if (p.Opcode & 0x00FF) == 0 { result[0] = 0 binary.BigEndian.PutUint16(result[1:3], p.Opcode) } else { binary.BigEndian.PutUint16(result[0:2], p.Opcode) } } // Copy data after opcode if p.Size > 0 { copy(result[opcodeBytes:], p.Buffer) } return result } // Copy creates a deep copy of the application packet func (p *EQApplicationPacket) Copy() *EQApplicationPacket { newPacket := &EQApplicationPacket{ EQPacket: NewEQPacket(p.Opcode, p.Buffer), EmuOpcode: p.EmuOpcode, OpcodeSize: p.OpcodeSize, } newPacket.CopyInfo(p.EQPacket) return newPacket } // PacketCombiner handles combining multiple packets for efficient transmission type PacketCombiner struct { maxSize int } // NewPacketCombiner creates a new packet combiner with max size limit func NewPacketCombiner(maxSize int) *PacketCombiner { return &PacketCombiner{maxSize: maxSize} } // CombineProtocolPackets combines multiple protocol packets into one func (c *PacketCombiner) CombineProtocolPackets(packets []*EQProtocolPacket) *EQProtocolPacket { if len(packets) == 0 { return nil } if len(packets) == 1 { return packets[0] } // Calculate total size needed totalSize := 0 for _, p := range packets { totalSize += 1 + int(p.TotalSize()) // 1 byte for size prefix } if totalSize > c.maxSize { return nil // Too large to combine } // Build combined packet buffer buffer := make([]byte, totalSize) offset := 0 for _, p := range packets { // Write size prefix buffer[offset] = byte(p.TotalSize()) offset++ // Serialize packet serialized := p.Serialize(0) copy(buffer[offset:], serialized) offset += len(serialized) } // Create combined packet combined := NewEQProtocolPacket(OPCombined, buffer) if len(packets) > 0 { combined.CopyInfo(packets[0].EQPacket) } return combined }