package packets import ( "encoding/binary" "time" "git.sharkk.net/EQ2/Protocol/opcodes" ) // ProtoPacket handles low-level protocol features including EQ2-specific operations // Merges functionality from EQProtocolPacket and EQ2Packet type ProtoPacket struct { *Packet // Protocol state flags (using bitfield) flags uint8 // bit 0: compressed, bit 1: prepared, bit 2: encrypted, bit 3: acked // EQ2-specific LoginOp opcodes.EmuOpcode // From EQ2Packet // Reliability and sequencing Sequence int32 SentTime int32 AttemptCount int8 // Opcode manager for translation manager opcodes.Manager // Compression/encoding settings CompressThreshold int EncodeKey int } // Protocol flag constants const ( FlagCompressed = 1 << iota FlagPrepared FlagEncrypted FlagAcked ) // Default compression threshold (compress packets larger than this) 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 a protocol packet from raw buffer 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, } // Convert EQ opcode to emulator opcode if manager available if pp.manager != nil { pp.LoginOp = pp.manager.EQToEmu(opcode) } return pp } // SetManager sets the opcode manager for translation func (p *ProtoPacket) SetManager(manager opcodes.Manager) { p.manager = manager } // IsCompressed returns true if packet is compressed func (p *ProtoPacket) IsCompressed() bool { return p.flags&FlagCompressed != 0 } // SetCompressed sets the compressed flag func (p *ProtoPacket) SetCompressed(compressed bool) { if compressed { p.flags |= FlagCompressed } else { p.flags &^= FlagCompressed } } // IsPrepared returns true if packet has been prepared for sending func (p *ProtoPacket) IsPrepared() bool { return p.flags&FlagPrepared != 0 } // SetPrepared sets the prepared flag func (p *ProtoPacket) SetPrepared(prepared bool) { if prepared { p.flags |= FlagPrepared } else { p.flags &^= FlagPrepared } } // IsEncrypted returns true if packet is encrypted func (p *ProtoPacket) IsEncrypted() bool { return p.flags&FlagEncrypted != 0 } // SetEncrypted sets the encrypted flag func (p *ProtoPacket) SetEncrypted(encrypted bool) { if encrypted { p.flags |= FlagEncrypted } else { p.flags &^= FlagEncrypted } } // IsAcked returns true if packet has been acknowledged func (p *ProtoPacket) IsAcked() bool { return p.flags&FlagAcked != 0 } // SetAcked sets the acknowledged flag func (p *ProtoPacket) SetAcked(acked bool) { if acked { p.flags |= FlagAcked } else { p.flags &^= FlagAcked } } // CompressPacket compresses the packet data if needed func (p *ProtoPacket) CompressPacket() error { if p.IsCompressed() || len(p.Buffer) <= p.CompressThreshold { return nil } compressed, err := Compress(p.Buffer) if err != nil { return err } // Only use compression if it actually saves space if len(compressed) < len(p.Buffer) { p.Buffer = compressed p.SetCompressed(true) } return nil } // DecompressPacket decompresses the packet data func (p *ProtoPacket) DecompressPacket() error { if !p.IsCompressed() { return nil } decompressed, err := Decompress(p.Buffer) if err != nil { return err } p.Buffer = decompressed p.SetCompressed(false) return nil } // EncodeChat applies chat encoding if this is a chat packet func (p *ProtoPacket) EncodeChat() { if p.EncodeKey == 0 || p.IsEncrypted() { return } if IsChatPacket(p.Opcode) { ChatEncode(p.Buffer, p.EncodeKey) p.SetEncrypted(true) } } // DecodeChat reverses chat encoding func (p *ProtoPacket) DecodeChat() { if p.EncodeKey == 0 || !p.IsEncrypted() { return } ChatDecode(p.Buffer, p.EncodeKey) p.SetEncrypted(false) } // Copy creates a deep copy of this protocol packet func (p *ProtoPacket) Copy() *ProtoPacket { newPacket := &ProtoPacket{ Packet: NewPacket(p.Opcode, p.Buffer), flags: p.flags, 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 } // Serialize writes the protocol packet to a destination buffer func (p *ProtoPacket) Serialize(dest []byte, offset int8) uint32 { // Apply compression before serialization p.CompressPacket() // Apply chat encoding if needed p.EncodeChat() // Write compression flag if compressed pos := 0 if p.IsCompressed() { dest[pos] = 0x5a // EQ compression flag pos++ } // Write opcode (2 bytes) if p.Opcode > 0xff { binary.BigEndian.PutUint16(dest[pos:], p.Opcode) } else { dest[pos] = 0 dest[pos+1] = byte(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) } // PreparePacket prepares an EQ2 packet for transmission (from EQ2Packet) func (p *ProtoPacket) PreparePacket(maxLen int16) int8 { if p.IsPrepared() { return 0 } p.SetPrepared(true) // Apply compression if needed if err := p.CompressPacket(); err != nil { return -1 } // Apply chat encoding if needed p.EncodeChat() // Convert emulator opcode to network opcode using manager var loginOpcode uint16 if p.manager != nil { loginOpcode = p.manager.EmuToEQ(p.LoginOp) } else { loginOpcode = uint16(p.LoginOp) } var offset int8 newSize := len(p.Buffer) + 2 + 1 // sequence(2) + compressed_flag(1) oversized := false // Add compression flag space if compressed if p.IsCompressed() { newSize++ } // Handle different opcode sizes and formats if loginOpcode != 2 { newSize++ // opcode type byte if loginOpcode >= 255 { newSize += 2 // oversized opcode oversized = true } } // Build new packet buffer newBuffer := make([]byte, newSize) ptr := 2 // Skip sequence field // Add compression flag if needed if p.IsCompressed() { newBuffer[ptr] = 0x5a // EQ compression flag ptr++ } 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(newSize - len(p.Buffer) - 1) return offset } // MakeApplicationPacket converts protocol packet to application packet func (p *ProtoPacket) MakeApplicationPacket(opcodeSize uint8) *AppPacket { // Decompress if needed p.DecompressPacket() // Decode chat if needed p.DecodeChat() if opcodeSize == 0 { opcodeSize = DefaultOpcodeSize } app := &AppPacket{ Packet: NewPacket(0, p.Buffer), opcodeSize: opcodeSize, manager: p.manager, } app.CopyInfo(p.Packet) // Parse opcode from buffer based on size if opcodeSize == 1 && len(p.Buffer) >= 1 { app.Opcode = uint16(p.Buffer[0]) app.Buffer = p.Buffer[1:] } else if len(p.Buffer) >= 2 { app.Opcode = binary.BigEndian.Uint16(p.Buffer[:2]) app.Buffer = p.Buffer[2:] } // Convert EQ opcode to emulator opcode if manager available if app.manager != nil { app.emuOpcode = app.manager.EQToEmu(app.Opcode) } return app } // AppCombine combines this packet with another for efficient transmission (from EQ2Packet) func (p *ProtoPacket) AppCombine(rhs *ProtoPacket) bool { const opAppCombined = 0x19 // OP_AppCombined value // Apply compression to both packets before combining p.CompressPacket() rhs.CompressPacket() // Case 1: This packet is already a combined packet if p.Opcode == opAppCombined && (len(p.Buffer)+len(rhs.Buffer)+3) < 255 { tmpSize := len(rhs.Buffer) - 2 overSized := tmpSize >= 255 var newSize int if overSized { newSize = len(p.Buffer) + tmpSize + 3 } else { newSize = len(p.Buffer) + tmpSize + 1 } tmpBuffer := make([]byte, newSize) pos := 0 // Copy existing combined packet data copy(tmpBuffer, p.Buffer) pos += len(p.Buffer) // Add size information for the new packet if overSized { tmpBuffer[pos] = 255 pos++ binary.BigEndian.PutUint16(tmpBuffer[pos:], uint16(tmpSize)) pos += 2 } else { tmpBuffer[pos] = byte(tmpSize) pos++ } // Copy the new packet data (skip first 2 bytes which are opcode) if len(rhs.Buffer) > 2 { copy(tmpBuffer[pos:], rhs.Buffer[2:]) } p.Buffer = tmpBuffer return true } // Case 2: Create new combined packet (simplified) return false }