1
0

convert packet management

This commit is contained in:
Sky Johnson 2025-07-02 13:25:43 -05:00
parent 6612d0731f
commit a1dd39d45a
7 changed files with 956 additions and 0 deletions

120
internal/packets/app.go Normal file
View File

@ -0,0 +1,120 @@
package packets
import (
"encoding/binary"
"eq2emu/internal/opcodes"
)
type AppHandler struct {
pool *PacketPool
}
func NewAppHandler(pool *PacketPool) *AppHandler {
return &AppHandler{pool: pool}
}
// Create application packet for sending
func (h *AppHandler) CreateAppPacket(opcode opcodes.AppOpcode, data []byte) *AppPacket {
packet := h.pool.GetAppPacket()
packet.Opcode = opcode
packet.Data = make([]byte, len(data))
copy(packet.Data, data)
return packet
}
// Serialize app packet to protocol packet
func (h *AppHandler) SerializeAppPacket(app *AppPacket, sequence uint16) []byte {
size := 3 + len(app.Data) // opcode(1) + seq(2) + opcode(2) + data
if app.Compressed {
size++ // compression flag
}
data := make([]byte, size)
offset := 0
data[offset] = byte(opcodes.OP_Packet)
offset++
binary.LittleEndian.PutUint16(data[offset:], sequence)
offset += 2
if app.Compressed {
data[offset] = 0xFF
offset++
}
binary.LittleEndian.PutUint16(data[offset:], uint16(app.Opcode))
offset += 2
copy(data[offset:], app.Data)
return data
}
// Create combined packet
func (h *AppHandler) SerializeCombinedPacket(apps []*AppPacket, sequence uint16) []byte {
// Calculate total size
totalSize := 3 // opcode + sequence
for _, app := range apps {
subSize := 2 + len(app.Data) // opcode + data
if subSize < 0xFF {
totalSize += 1 + subSize
} else {
totalSize += 3 + subSize
}
}
data := make([]byte, totalSize)
offset := 0
data[offset] = byte(opcodes.OP_Combined)
offset++
binary.LittleEndian.PutUint16(data[offset:], sequence)
offset += 2
for _, app := range apps {
subSize := 2 + len(app.Data)
// Write length
if subSize < 0xFF {
data[offset] = byte(subSize)
offset++
} else {
data[offset] = 0xFF
offset++
binary.LittleEndian.PutUint16(data[offset:], uint16(subSize))
offset += 2
}
// Write opcode
binary.LittleEndian.PutUint16(data[offset:], uint16(app.Opcode))
offset += 2
// Write data
copy(data[offset:], app.Data)
offset += len(app.Data)
}
return data
}
// Common EQ2 packet helpers
func (h *AppHandler) CreateChatPacket(message string, channel uint8) *AppPacket {
writer := NewPacketWriter()
writer.WriteUint8(channel)
writer.WriteEQ2String(message)
return h.CreateAppPacket(opcodes.OP_EqHearChatCmd, writer.Bytes())
}
func (h *AppHandler) CreatePositionUpdatePacket(playerID uint32, x, y, z float32) *AppPacket {
writer := NewPacketWriter()
writer.WriteUint32(playerID)
writer.WriteFloat32(x)
writer.WriteFloat32(y)
writer.WriteFloat32(z)
return h.CreateAppPacket(opcodes.OP_UpdatePositionMsg, writer.Bytes())
}

View File

@ -0,0 +1,64 @@
package packets
import (
"bytes"
"compress/zlib"
"io"
)
type EQ2Compressor struct {
level int
}
func NewEQ2Compressor() *EQ2Compressor {
return &EQ2Compressor{level: zlib.DefaultCompression}
}
func (c *EQ2Compressor) Compress(data []byte) ([]byte, error) {
if len(data) == 0 {
return data, nil
}
var buf bytes.Buffer
writer, err := zlib.NewWriterLevel(&buf, c.level)
if err != nil {
return nil, err
}
_, err = writer.Write(data)
if err != nil {
writer.Close()
return nil, err
}
err = writer.Close()
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (c *EQ2Compressor) Decompress(data []byte) ([]byte, error) {
if len(data) == 0 {
return data, nil
}
reader, err := zlib.NewReader(bytes.NewReader(data))
if err != nil {
return nil, err
}
defer reader.Close()
var buf bytes.Buffer
_, err = io.Copy(&buf, reader)
if err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func (c *EQ2Compressor) ShouldCompress(data []byte) bool {
return len(data) > 100 // EQ2 compression threshold
}

View File

@ -0,0 +1,202 @@
package packets
import (
"encoding/binary"
"fmt"
"eq2emu/internal/opcodes"
)
type ProtocolHandler struct {
pool *PacketPool
}
func NewProtocolHandler(pool *PacketPool) *ProtocolHandler {
return &ProtocolHandler{pool: pool}
}
// Parse EQ2 protocol packet from gnet data
func (h *ProtocolHandler) ParsePacket(data []byte) (*EQ2Packet, error) {
if len(data) < 1 {
return nil, fmt.Errorf("packet too small")
}
packet := h.pool.GetEQ2Packet()
packet.Opcode = opcodes.ProtocolOpcode(data[0])
if len(data) > 1 {
packet.Data = make([]byte, len(data)-1)
copy(packet.Data, data[1:])
}
// Extract sequence if present
if packet.Opcode == opcodes.OP_Packet || packet.Opcode == opcodes.OP_Combined {
if len(packet.Data) >= 2 {
packet.Sequence = binary.LittleEndian.Uint16(packet.Data[:2])
}
}
return packet, nil
}
// Extract application packets from protocol packet
func (h *ProtocolHandler) ExtractAppPackets(packet *EQ2Packet, session *Session) ([]*AppPacket, error) {
switch packet.Opcode {
case opcodes.OP_Packet:
return h.extractSingleApp(packet.Data)
case opcodes.OP_Combined:
return h.extractCombinedApps(packet.Data)
case opcodes.OP_Fragment:
return h.handleFragment(packet, session)
default:
return nil, nil
}
}
func (h *ProtocolHandler) extractSingleApp(data []byte) ([]*AppPacket, error) {
if len(data) < 4 { // seq(2) + opcode(2)
return nil, fmt.Errorf("packet too small")
}
offset := 2 // Skip sequence
// Check compression flag
compressed := false
if data[offset] == 0xFF {
compressed = true
offset++
}
if len(data) < offset+2 {
return nil, fmt.Errorf("insufficient data")
}
opcode := binary.LittleEndian.Uint16(data[offset:])
payload := data[offset+2:]
app := h.pool.GetAppPacket()
app.Opcode = opcodes.AppOpcode(opcode)
app.Data = make([]byte, len(payload))
copy(app.Data, payload)
app.Compressed = compressed
return []*AppPacket{app}, nil
}
func (h *ProtocolHandler) extractCombinedApps(data []byte) ([]*AppPacket, error) {
if len(data) < 2 {
return nil, fmt.Errorf("combined packet too small")
}
remaining := data[2:] // Skip sequence
var apps []*AppPacket
for len(remaining) > 0 {
if len(remaining) < 1 {
break
}
// Get sub-packet length
subLen := uint16(remaining[0])
offset := 1
if subLen == 0xFF {
if len(remaining) < 3 {
break
}
subLen = binary.LittleEndian.Uint16(remaining[1:3])
offset = 3
}
if len(remaining) < offset+int(subLen) {
break
}
subData := remaining[offset : offset+int(subLen)]
remaining = remaining[offset+int(subLen):]
if len(subData) < 2 {
continue
}
opcode := binary.LittleEndian.Uint16(subData[:2])
payload := subData[2:]
app := h.pool.GetAppPacket()
app.Opcode = opcodes.AppOpcode(opcode)
app.Data = make([]byte, len(payload))
copy(app.Data, payload)
apps = append(apps, app)
}
return apps, nil
}
func (h *ProtocolHandler) handleFragment(packet *EQ2Packet, session *Session) ([]*AppPacket, error) {
if len(packet.Data) < 6 { // seq(2) + fragInfo(2) + data
return nil, fmt.Errorf("fragment too small")
}
sequence := packet.Sequence
fragInfo := binary.LittleEndian.Uint16(packet.Data[2:4])
fragData := packet.Data[4:]
current := fragInfo & 0xFF
total := fragInfo >> 8
// Get or create fragment buffer
fragBuffer := session.Fragments[sequence]
if fragBuffer == nil {
fragBuffer = &FragmentBuffer{
Fragments: make(map[uint16][]byte),
Total: total,
Received: 0,
}
session.Fragments[sequence] = fragBuffer
}
// Store fragment
fragBuffer.Fragments[current] = make([]byte, len(fragData))
copy(fragBuffer.Fragments[current], fragData)
fragBuffer.Received++
// Check if complete
if fragBuffer.Received == fragBuffer.Total {
// Reassemble
var complete []byte
for i := uint16(0); i < fragBuffer.Total; i++ {
complete = append(complete, fragBuffer.Fragments[i]...)
}
// Clean up
delete(session.Fragments, sequence)
// Process as normal packet
return h.extractSingleApp(complete)
}
return nil, nil
}
// Create protocol packets
func (h *ProtocolHandler) CreateSessionRequest(sessionID uint32, maxLen uint32) []byte {
data := make([]byte, 13)
data[0] = byte(opcodes.OP_SessionRequest)
binary.LittleEndian.PutUint32(data[1:5], sessionID)
binary.LittleEndian.PutUint32(data[5:9], maxLen)
return data
}
func (h *ProtocolHandler) CreateSessionResponse(sessionID uint32, key uint32, format uint8) []byte {
data := make([]byte, 10)
data[0] = byte(opcodes.OP_SessionResponse)
binary.LittleEndian.PutUint32(data[1:5], sessionID)
binary.LittleEndian.PutUint32(data[5:9], key)
data[9] = format
return data
}
func (h *ProtocolHandler) CreateKeepAlive() []byte {
return []byte{byte(opcodes.OP_KeepAlive)}
}

263
internal/packets/reader.go Normal file
View File

@ -0,0 +1,263 @@
package packets
import (
"encoding/binary"
"fmt"
"io"
"unsafe"
)
// PacketReader for efficient packet parsing
type PacketReader struct {
data []byte
offset int
}
func NewPacketReader(data []byte) *PacketReader {
return &PacketReader{
data: data,
offset: 0,
}
}
func (r *PacketReader) Remaining() int {
return len(r.data) - r.offset
}
func (r *PacketReader) Position() int {
return r.offset
}
func (r *PacketReader) Seek(pos int) error {
if pos < 0 || pos > len(r.data) {
return fmt.Errorf("seek position out of bounds")
}
r.offset = pos
return nil
}
func (r *PacketReader) Skip(n int) error {
if r.offset+n > len(r.data) {
return io.ErrUnexpectedEOF
}
r.offset += n
return nil
}
// Read raw bytes
func (r *PacketReader) ReadBytes(n int) ([]byte, error) {
if r.offset+n > len(r.data) {
return nil, io.ErrUnexpectedEOF
}
result := make([]byte, n)
copy(result, r.data[r.offset:r.offset+n])
r.offset += n
return result, nil
}
func (r *PacketReader) ReadBytesRef(n int) ([]byte, error) {
if r.offset+n > len(r.data) {
return nil, io.ErrUnexpectedEOF
}
result := r.data[r.offset : r.offset+n]
r.offset += n
return result, nil
}
// Read integers
func (r *PacketReader) ReadUint8() (uint8, error) {
if r.offset >= len(r.data) {
return 0, io.ErrUnexpectedEOF
}
result := r.data[r.offset]
r.offset++
return result, nil
}
func (r *PacketReader) ReadInt8() (int8, error) {
val, err := r.ReadUint8()
return int8(val), err
}
func (r *PacketReader) ReadUint16() (uint16, error) {
if r.offset+2 > len(r.data) {
return 0, io.ErrUnexpectedEOF
}
result := binary.LittleEndian.Uint16(r.data[r.offset:])
r.offset += 2
return result, nil
}
func (r *PacketReader) ReadInt16() (int16, error) {
val, err := r.ReadUint16()
return int16(val), err
}
func (r *PacketReader) ReadUint32() (uint32, error) {
if r.offset+4 > len(r.data) {
return 0, io.ErrUnexpectedEOF
}
result := binary.LittleEndian.Uint32(r.data[r.offset:])
r.offset += 4
return result, nil
}
func (r *PacketReader) ReadInt32() (int32, error) {
val, err := r.ReadUint32()
return int32(val), err
}
func (r *PacketReader) ReadUint64() (uint64, error) {
if r.offset+8 > len(r.data) {
return 0, io.ErrUnexpectedEOF
}
result := binary.LittleEndian.Uint64(r.data[r.offset:])
r.offset += 8
return result, nil
}
func (r *PacketReader) ReadInt64() (int64, error) {
val, err := r.ReadUint64()
return int64(val), err
}
func (r *PacketReader) ReadFloat32() (float32, error) {
if r.offset+4 > len(r.data) {
return 0, io.ErrUnexpectedEOF
}
bits := binary.LittleEndian.Uint32(r.data[r.offset:])
result := *(*float32)(unsafe.Pointer(&bits))
r.offset += 4
return result, nil
}
func (r *PacketReader) ReadFloat64() (float64, error) {
if r.offset+8 > len(r.data) {
return 0, io.ErrUnexpectedEOF
}
bits := binary.LittleEndian.Uint64(r.data[r.offset:])
result := *(*float64)(unsafe.Pointer(&bits))
r.offset += 8
return result, nil
}
// Read strings
func (r *PacketReader) ReadString() (string, error) {
length, err := r.ReadUint16()
if err != nil {
return "", err
}
if r.offset+int(length) > len(r.data) {
return "", io.ErrUnexpectedEOF
}
result := string(r.data[r.offset : r.offset+int(length)])
r.offset += int(length)
return result, nil
}
func (r *PacketReader) ReadStringFixed(length int) (string, error) {
if r.offset+length > len(r.data) {
return "", io.ErrUnexpectedEOF
}
// Find null terminator
end := r.offset + length
for i := r.offset; i < end; i++ {
if r.data[i] == 0 {
end = i
break
}
}
result := string(r.data[r.offset:end])
r.offset += length
return result, nil
}
func (r *PacketReader) ReadCString() (string, error) {
start := r.offset
for r.offset < len(r.data) && r.data[r.offset] != 0 {
r.offset++
}
if r.offset >= len(r.data) {
return "", io.ErrUnexpectedEOF
}
result := string(r.data[start:r.offset])
r.offset++ // Skip null terminator
return result, nil
}
// EQ2-specific reads
func (r *PacketReader) ReadEQ2String() (string, error) {
// EQ2 strings are prefixed with length as uint16
return r.ReadString()
}
func (r *PacketReader) ReadPackedInt() (uint32, error) {
// EQ2 packed integer format
val, err := r.ReadUint8()
if err != nil {
return 0, err
}
if val < 0xFF {
return uint32(val), nil
}
// Extended format
val2, err := r.ReadUint16()
if err != nil {
return 0, err
}
if val2 < 0xFFFF {
return uint32(val2), nil
}
// Full 32-bit
return r.ReadUint32()
}
func (r *PacketReader) ReadBool() (bool, error) {
val, err := r.ReadUint8()
return val != 0, err
}
// Peek without advancing
func (r *PacketReader) PeekUint8() (uint8, error) {
if r.offset >= len(r.data) {
return 0, io.ErrUnexpectedEOF
}
return r.data[r.offset], nil
}
func (r *PacketReader) PeekUint16() (uint16, error) {
if r.offset+2 > len(r.data) {
return 0, io.ErrUnexpectedEOF
}
return binary.LittleEndian.Uint16(r.data[r.offset:]), nil
}
func (r *PacketReader) PeekUint32() (uint32, error) {
if r.offset+4 > len(r.data) {
return 0, io.ErrUnexpectedEOF
}
return binary.LittleEndian.Uint32(r.data[r.offset:]), nil
}
// Buffer management
func (r *PacketReader) Reset(data []byte) {
r.data = data
r.offset = 0
}
func (r *PacketReader) Clone() *PacketReader {
return &PacketReader{
data: r.data,
offset: r.offset,
}
}

View File

@ -0,0 +1,75 @@
package packets
import (
"sync"
"time"
)
type SessionManager struct {
sessions map[uint32]*Session
mu sync.RWMutex
}
func NewSessionManager() *SessionManager {
return &SessionManager{
sessions: make(map[uint32]*Session),
}
}
func (sm *SessionManager) GetSession(id uint32) *Session {
sm.mu.RLock()
defer sm.mu.RUnlock()
return sm.sessions[id]
}
func (sm *SessionManager) CreateSession(id uint32) *Session {
sm.mu.Lock()
defer sm.mu.Unlock()
session := &Session{
ID: id,
Established: false,
NextSeq: 0,
LastKeep: time.Now(),
Compressed: false,
Fragments: make(map[uint16]*FragmentBuffer),
}
sm.sessions[id] = session
return session
}
func (sm *SessionManager) RemoveSession(id uint32) {
sm.mu.Lock()
defer sm.mu.Unlock()
delete(sm.sessions, id)
}
func (sm *SessionManager) UpdateKeepAlive(id uint32) {
sm.mu.Lock()
defer sm.mu.Unlock()
if session := sm.sessions[id]; session != nil {
session.LastKeep = time.Now()
}
}
func (sm *SessionManager) GetNextSequence(id uint32) uint16 {
sm.mu.Lock()
defer sm.mu.Unlock()
if session := sm.sessions[id]; session != nil {
seq := session.NextSeq
session.NextSeq++
return seq
}
return 0
}
func (sm *SessionManager) SetEstablished(id uint32, established bool) {
sm.mu.Lock()
defer sm.mu.Unlock()
if session := sm.sessions[id]; session != nil {
session.Established = established
}
}

88
internal/packets/types.go Normal file
View File

@ -0,0 +1,88 @@
package packets
import (
"time"
"eq2emu/internal/opcodes"
)
// EQ2 protocol packet
type EQ2Packet struct {
Opcode opcodes.ProtocolOpcode
Data []byte
Sequence uint16
}
// EQ2 application packet
type AppPacket struct {
Opcode opcodes.AppOpcode
Data []byte
Compressed bool
}
// Session for EQ2 connection state
type Session struct {
ID uint32
Established bool
NextSeq uint16
LastKeep time.Time
Compressed bool
Fragments map[uint16]*FragmentBuffer
}
// Fragment reassembly
type FragmentBuffer struct {
Fragments map[uint16][]byte
Total uint16
Received uint16
Timestamp time.Time
}
// Packet pools
type PacketPool struct {
eq2Packets chan *EQ2Packet
appPackets chan *AppPacket
}
func NewPacketPool(size int) *PacketPool {
return &PacketPool{
eq2Packets: make(chan *EQ2Packet, size),
appPackets: make(chan *AppPacket, size),
}
}
func (p *PacketPool) GetEQ2Packet() *EQ2Packet {
select {
case packet := <-p.eq2Packets:
return packet
default:
return &EQ2Packet{}
}
}
func (p *PacketPool) PutEQ2Packet(packet *EQ2Packet) {
packet.Data = packet.Data[:0]
packet.Opcode = 0
select {
case p.eq2Packets <- packet:
default:
}
}
func (p *PacketPool) GetAppPacket() *AppPacket {
select {
case packet := <-p.appPackets:
return packet
default:
return &AppPacket{}
}
}
func (p *PacketPool) PutAppPacket(packet *AppPacket) {
packet.Data = packet.Data[:0]
packet.Opcode = 0
select {
case p.appPackets <- packet:
default:
}
}

144
internal/packets/writer.go Normal file
View File

@ -0,0 +1,144 @@
package packets
import (
"encoding/binary"
"unsafe"
)
type PacketWriter struct {
data []byte
}
func NewPacketWriter() *PacketWriter {
return &PacketWriter{
data: make([]byte, 0, 512),
}
}
func NewPacketWriterSize(size int) *PacketWriter {
return &PacketWriter{
data: make([]byte, 0, size),
}
}
func (w *PacketWriter) Bytes() []byte {
return w.data
}
func (w *PacketWriter) Len() int {
return len(w.data)
}
func (w *PacketWriter) Reset() {
w.data = w.data[:0]
}
func (w *PacketWriter) WriteBytes(data []byte) {
w.data = append(w.data, data...)
}
func (w *PacketWriter) WriteUint8(val uint8) {
w.data = append(w.data, val)
}
func (w *PacketWriter) WriteInt8(val int8) {
w.WriteUint8(uint8(val))
}
func (w *PacketWriter) WriteUint16(val uint16) {
b := make([]byte, 2)
binary.LittleEndian.PutUint16(b, val)
w.data = append(w.data, b...)
}
func (w *PacketWriter) WriteInt16(val int16) {
w.WriteUint16(uint16(val))
}
func (w *PacketWriter) WriteUint32(val uint32) {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, val)
w.data = append(w.data, b...)
}
func (w *PacketWriter) WriteInt32(val int32) {
w.WriteUint32(uint32(val))
}
func (w *PacketWriter) WriteUint64(val uint64) {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, val)
w.data = append(w.data, b...)
}
func (w *PacketWriter) WriteInt64(val int64) {
w.WriteUint64(uint64(val))
}
func (w *PacketWriter) WriteFloat32(val float32) {
bits := *(*uint32)(unsafe.Pointer(&val))
w.WriteUint32(bits)
}
func (w *PacketWriter) WriteFloat64(val float64) {
bits := *(*uint64)(unsafe.Pointer(&val))
w.WriteUint64(bits)
}
func (w *PacketWriter) WriteString(s string) {
w.WriteUint16(uint16(len(s)))
w.data = append(w.data, []byte(s)...)
}
func (w *PacketWriter) WriteStringFixed(s string, length int) {
data := make([]byte, length)
copy(data, []byte(s))
w.data = append(w.data, data...)
}
func (w *PacketWriter) WriteCString(s string) {
w.data = append(w.data, []byte(s)...)
w.data = append(w.data, 0)
}
func (w *PacketWriter) WriteEQ2String(s string) {
w.WriteString(s)
}
func (w *PacketWriter) WritePackedInt(val uint32) {
if val < 0xFF {
w.WriteUint8(uint8(val))
} else if val < 0xFFFF {
w.WriteUint8(0xFF)
w.WriteUint16(uint16(val))
} else {
w.WriteUint8(0xFF)
w.WriteUint16(0xFFFF)
w.WriteUint32(val)
}
}
func (w *PacketWriter) WriteBool(val bool) {
if val {
w.WriteUint8(1)
} else {
w.WriteUint8(0)
}
}
func (w *PacketWriter) WriteZeros(count int) {
zeros := make([]byte, count)
w.data = append(w.data, zeros...)
}
func (w *PacketWriter) SetUint16At(pos int, val uint16) {
if pos+1 < len(w.data) {
binary.LittleEndian.PutUint16(w.data[pos:], val)
}
}
func (w *PacketWriter) SetUint32At(pos int, val uint32) {
if pos+3 < len(w.data) {
binary.LittleEndian.PutUint32(w.data[pos:], val)
}
}