package packets import ( "encoding/binary" "time" "git.sharkk.net/EQ2/Protocol/opcodes" ) // ProtoPacket handles low-level protocol features (matches EQProtocolPacket/EQ2Packet) type ProtoPacket struct { *Packet // Protocol state flags eq2Compressed bool packetPrepared bool packetEncrypted bool acked bool // EQ2-specific LoginOp opcodes.EmuOpcode // Reliability and sequencing Sequence int32 SentTime int32 AttemptCount int8 // Opcode manager for translation manager opcodes.Manager // Compression/encoding settings CompressThreshold int EncodeKey int } const DefaultCompressThreshold = 100 // NewProtoPacket creates a protocol packet with opcode and buffer func NewProtoPacket(op uint16, buf []byte, manager opcodes.Manager) *ProtoPacket { return &ProtoPacket{ Packet: NewPacket(op, buf), manager: manager, CompressThreshold: DefaultCompressThreshold, } } // NewProtoPacketFromRaw creates from raw buffer (matches EQProtocolPacket constructor) func NewProtoPacketFromRaw(buf []byte, opcodeOverride int, manager opcodes.Manager) *ProtoPacket { var offset uint32 var opcode uint16 if opcodeOverride >= 0 { opcode = uint16(opcodeOverride) } else if len(buf) >= 2 { opcode = binary.BigEndian.Uint16(buf[:2]) offset = 2 } var data []byte if uint32(len(buf)) > offset { data = make([]byte, len(buf)-int(offset)) copy(data, buf[offset:]) } pp := &ProtoPacket{ Packet: &Packet{ Opcode: opcode, Buffer: data, Timestamp: time.Now(), }, manager: manager, CompressThreshold: DefaultCompressThreshold, } if pp.manager != nil { pp.LoginOp = pp.manager.EQToEmu(opcode) } return pp } // PreparePacket prepares an EQ2 packet for transmission (matches EQ2Packet::PreparePacket) func (p *ProtoPacket) PreparePacket(maxLen int16) int8 { if p.packetPrepared { return 0 } if p.manager == nil { return -1 } p.packetPrepared = true // Convert emulator opcode to network opcode loginOpcode := p.manager.EmuToEQ(p.LoginOp) // Apply compression if needed if !p.eq2Compressed && len(p.Buffer) > p.CompressThreshold { compressed, err := Compress(p.Buffer) if err == nil && len(compressed) < len(p.Buffer) { p.Buffer = compressed p.eq2Compressed = true } } // Build new packet buffer with proper headers var offset int8 newSize := len(p.Buffer) + 2 // Base size with sequence // Add compression flag space if compressed if p.eq2Compressed { newSize++ } // Handle opcode encoding (matches C++ implementation) oversized := false if loginOpcode != 2 { // Not OP_SessionResponse if loginOpcode >= 255 { newSize += 3 // oversized opcode (0xFF + 2 bytes) oversized = true } else { newSize += 2 // normal opcode } } else { newSize++ // single byte for OP_SessionResponse } // Build new buffer newBuffer := make([]byte, newSize) ptr := 2 // Skip sequence field (filled by stream) // Add compression flag if p.eq2Compressed { newBuffer[ptr] = 0x5a // EQ2 compression flag ptr++ } // Encode opcode if loginOpcode != 2 { if oversized { newBuffer[ptr] = 0xff // Oversized marker ptr++ binary.BigEndian.PutUint16(newBuffer[ptr:], loginOpcode) ptr += 2 } else { binary.BigEndian.PutUint16(newBuffer[ptr:], loginOpcode) ptr += 2 } } else { newBuffer[ptr] = byte(loginOpcode) ptr++ } // Copy original packet data copy(newBuffer[ptr:], p.Buffer) p.Buffer = newBuffer offset = int8(ptr - 2) // Return offset past sequence field return offset } // IsCompressed returns whether the packet is compressed func (p *ProtoPacket) IsCompressed() bool { return p.eq2Compressed } // Serialize writes the protocol packet to a destination buffer func (p *ProtoPacket) Serialize(dest []byte, offset int8) uint32 { pos := 0 // Write compression flag if compressed if p.eq2Compressed { dest[pos] = 0x5a pos++ } // Write opcode (2 bytes) binary.BigEndian.PutUint16(dest[pos:], p.Opcode) pos += 2 // Copy packet data after opcode if offset < int8(len(p.Buffer)) { copy(dest[pos:], p.Buffer[offset:]) return uint32(len(p.Buffer)-int(offset)) + uint32(pos) } return uint32(pos) } // Combine combines this protocol packet with another (matches EQProtocolPacket::combine) func (p *ProtoPacket) Combine(rhs *ProtoPacket) bool { const opCombined = 0x03 // OP_Combined // Case 1: This packet is already combined - append to it if p.Opcode == opCombined && p.Size()+rhs.Size()+3 < 256 { newSize := len(p.Buffer) + int(rhs.Size()) + 1 newBuffer := make([]byte, newSize) // Copy existing combined data copy(newBuffer, p.Buffer) offset := len(p.Buffer) // Add size prefix for new packet newBuffer[offset] = byte(rhs.Size()) offset++ // Serialize and append new packet tmpBuf := make([]byte, rhs.Size()) rhs.Serialize(tmpBuf, 0) copy(newBuffer[offset:], tmpBuf) p.Buffer = newBuffer return true } // Case 2: Neither packet is combined - create new combined packet if p.Size()+rhs.Size()+4 < 256 { totalSize := int(p.Size()) + int(rhs.Size()) + 2 newBuffer := make([]byte, totalSize) offset := 0 // Add first packet with size prefix newBuffer[offset] = byte(p.Size()) offset++ tmpBuf := make([]byte, p.Size()) p.Serialize(tmpBuf, 0) copy(newBuffer[offset:], tmpBuf) offset += int(p.Size()) // Add second packet with size prefix newBuffer[offset] = byte(rhs.Size()) offset++ tmpBuf = make([]byte, rhs.Size()) rhs.Serialize(tmpBuf, 0) copy(newBuffer[offset:], tmpBuf) // Update buffer and mark as combined p.Buffer = newBuffer p.Opcode = opCombined return true } return false } // AppCombine combines app-level packets (matches EQ2Packet::AppCombine) func (p *ProtoPacket) AppCombine(rhs *ProtoPacket) bool { const opAppCombined = 0x19 // OP_AppCombined lhsSize := p.Size() rhsSize := rhs.Size() // Check max combined size if lhsSize+rhsSize > MaxCombinedSize { return false } // If already combined, add to it if p.Opcode == opAppCombined { tmpSize := rhsSize - 2 // Subtract opcode bytes var newSize int if tmpSize >= 255 { newSize = len(p.Buffer) + int(tmpSize) + 3 // oversized header } else { newSize = len(p.Buffer) + int(tmpSize) + 1 // normal header } newBuffer := make([]byte, newSize) copy(newBuffer, p.Buffer) pos := len(p.Buffer) // Add size header if tmpSize >= 255 { newBuffer[pos] = 255 // Oversized marker pos++ binary.BigEndian.PutUint16(newBuffer[pos:], uint16(tmpSize)) pos += 2 } else { newBuffer[pos] = byte(tmpSize) pos++ } // Serialize rhs packet (skip first 2 bytes - opcode) if len(rhs.Buffer) > 2 { copy(newBuffer[pos:], rhs.Buffer[2:]) } p.Buffer = newBuffer return true } // Create new combined packet lSize := lhsSize - 2 rSize := rhsSize - 2 totalSize := 0 if lSize >= 255 { totalSize += 3 + int(lSize) } else { totalSize += 1 + int(lSize) } if rSize >= 255 { totalSize += 3 + int(rSize) } else { totalSize += 1 + int(rSize) } if totalSize > MaxCombinedSize { return false } newBuffer := make([]byte, totalSize) pos := 0 // Add first packet if lSize >= 255 { newBuffer[pos] = 255 pos++ binary.BigEndian.PutUint16(newBuffer[pos:], uint16(lSize)) pos += 2 } else { newBuffer[pos] = byte(lSize) pos++ } if len(p.Buffer) > 2 { copy(newBuffer[pos:], p.Buffer[2:]) pos += int(lSize) } // Add second packet if rSize >= 255 { newBuffer[pos] = 255 pos++ binary.BigEndian.PutUint16(newBuffer[pos:], uint16(rSize)) pos += 2 } else { newBuffer[pos] = byte(rSize) pos++ } if len(rhs.Buffer) > 2 { copy(newBuffer[pos:], rhs.Buffer[2:]) } p.Opcode = opAppCombined p.Buffer = newBuffer p.packetPrepared = false return true } // MakeApplicationPacket converts to app packet (matches EQProtocolPacket::MakeApplicationPacket) func (p *ProtoPacket) MakeApplicationPacket(opcodeSize uint8) *AppPacket { // Decompress if needed if p.eq2Compressed { if decompressed, err := Decompress(p.Buffer); err == nil { p.Buffer = decompressed p.eq2Compressed = false } } // Decode chat if needed if p.packetEncrypted && p.EncodeKey != 0 { ChatDecode(p.Buffer, p.EncodeKey) p.packetEncrypted = false } return NewAppPacketFromRaw(p.Buffer, opcodeSize, p.manager) } // Copy creates a deep copy func (p *ProtoPacket) Copy() *ProtoPacket { newPacket := &ProtoPacket{ Packet: NewPacket(p.Opcode, p.Buffer), eq2Compressed: p.eq2Compressed, packetPrepared: p.packetPrepared, packetEncrypted: p.packetEncrypted, acked: p.acked, LoginOp: p.LoginOp, Sequence: p.Sequence, SentTime: p.SentTime, AttemptCount: p.AttemptCount, manager: p.manager, CompressThreshold: p.CompressThreshold, EncodeKey: p.EncodeKey, } newPacket.Packet.CopyInfo(p.Packet) return newPacket }