137 lines
3.8 KiB
Go
137 lines
3.8 KiB
Go
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
|
|
}
|
|
}
|