package udp import ( "encoding/binary" "eq2emu/internal/opcodes" "errors" "fmt" ) // ProtocolPacket represents a low-level UDP protocol packet with opcode and payload type ProtocolPacket struct { Opcode uint8 // Protocol operation code (1-2 bytes when serialized) Data []byte // Packet payload data Raw []byte // Original raw packet data for debugging } // ApplicationPacket represents a higher-level game application packet type ApplicationPacket struct { Opcode uint16 // Application-level operation code Data []byte // Application payload data } // ParseProtocolPacket parses raw UDP data into a ProtocolPacket // Handles variable opcode sizing and CRC validation based on EQ2 protocol func ParseProtocolPacket(data []byte) (*ProtocolPacket, error) { if len(data) < 2 { return nil, errors.New("packet too small for valid protocol packet") } var opcode uint8 var dataStart int // EQ2 protocol uses 1-byte opcodes normally, 2-byte for opcodes >= 0xFF // When opcode >= 0xFF, it's prefixed with 0x00 if data[0] == 0x00 && len(data) > 2 { opcode = data[1] dataStart = 2 } else { opcode = data[0] dataStart = 1 } // Extract payload, handling CRC for non-session packets var payload []byte if requiresCRC(opcode) { if len(data) < dataStart+2 { return nil, errors.New("packet too small for CRC validation") } // Payload excludes the 2-byte CRC suffix payload = data[dataStart : len(data)-2] // Validate CRC on the entire packet from beginning if !ValidateCRC(data) { return nil, fmt.Errorf("CRC validation failed for opcode 0x%02X", opcode) } } else { payload = data[dataStart:] } return &ProtocolPacket{ Opcode: opcode, Data: payload, Raw: data, }, nil } // Serialize converts ProtocolPacket back to wire format with proper opcode encoding and CRC func (p *ProtocolPacket) Serialize() []byte { var result []byte // Handle variable opcode encoding if p.Opcode == 0xFF { // 2-byte opcode format: [0x00][actual_opcode][data] result = make([]byte, 2+len(p.Data)) result[0] = 0x00 result[1] = p.Opcode copy(result[2:], p.Data) } else { // 1-byte opcode format: [opcode][data] result = make([]byte, 1+len(p.Data)) result[0] = p.Opcode copy(result[1:], p.Data) } // Add CRC for packets that require it if requiresCRC(p.Opcode) { result = AppendCRC(result) } return result } // ParseApplicationPacket parses application-level packet from decrypted/decompressed data func ParseApplicationPacket(data []byte) (*ApplicationPacket, error) { if len(data) < 2 { return nil, errors.New("application packet requires at least 2 bytes for opcode") } // Application opcodes are always little-endian 16-bit values opcode := binary.LittleEndian.Uint16(data[0:2]) return &ApplicationPacket{ Opcode: opcode, Data: data[2:], }, nil } // Serialize converts ApplicationPacket to byte array for transmission func (p *ApplicationPacket) Serialize() []byte { result := make([]byte, 2+len(p.Data)) binary.LittleEndian.PutUint16(result[0:2], p.Opcode) copy(result[2:], p.Data) return result } // String provides human-readable representation for debugging func (p *ProtocolPacket) String() string { return fmt.Sprintf("ProtocolPacket{Opcode: 0x%02X, DataLen: %d}", p.Opcode, len(p.Data)) } // String provides human-readable representation for debugging func (p *ApplicationPacket) String() string { return fmt.Sprintf("ApplicationPacket{Opcode: 0x%04X, DataLen: %d}", p.Opcode, len(p.Data)) } // requiresCRC determines if a protocol opcode requires CRC validation // Session control packets (SessionRequest, SessionResponse, OutOfSession) don't use CRC func requiresCRC(opcode uint8) bool { switch opcode { case opcodes.OpSessionRequest, opcodes.OpSessionResponse, opcodes.OpOutOfSession: return false default: return true } }