package packets import ( "encoding/binary" "time" ) // 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 EmuOpcode // From EQ2Packet // Reliability and sequencing Sequence int32 SentTime int32 AttemptCount int8 } // Protocol flag constants const ( FlagCompressed = 1 << iota FlagPrepared FlagEncrypted FlagAcked ) // NewProtoPacket creates a protocol packet with opcode and buffer func NewProtoPacket(op uint16, buf []byte) *ProtoPacket { return &ProtoPacket{ Packet: NewPacket(op, buf), } } // NewProtoPacketFromRaw creates a protocol packet from raw buffer func NewProtoPacketFromRaw(buf []byte, opcodeOverride int) *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:]) } return &ProtoPacket{ Packet: &Packet{ Opcode: opcode, Buffer: data, Timestamp: time.Now(), }, } } // 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 } } // 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, } 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 { // Write opcode (2 bytes) if p.Opcode > 0xff { binary.BigEndian.PutUint16(dest, p.Opcode) } else { dest[0] = 0 dest[1] = byte(p.Opcode) } // Copy packet data after opcode if offset < int8(len(p.Buffer)) { copy(dest[2:], p.Buffer[offset:]) } return uint32(len(p.Buffer)-int(offset)) + 2 } // AppCombine combines this packet with another for efficient transmission (from EQ2Packet) func (p *ProtoPacket) AppCombine(rhs *ProtoPacket) bool { const opAppCombined = 0x19 // OP_AppCombined value // 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 // Implementation would continue here for other combine cases // Simplified for brevity return false } // PreparePacket prepares an EQ2 packet for transmission (from EQ2Packet) func (p *ProtoPacket) PreparePacket(maxLen int16) int8 { if p.IsPrepared() { return 0 } p.SetPrepared(true) // Convert emulator opcode to network opcode // This would use opcode manager in full implementation loginOpcode := p.LoginOp // Simplified - would do actual conversion var offset int8 newSize := len(p.Buffer) + 2 + 1 // sequence(2) + compressed_flag(1) oversized := false // 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 if loginOpcode != 2 { if oversized { ptr++ // Skip compressed flag position newBuffer[ptr] = 0xff // Oversized marker ptr++ binary.BigEndian.PutUint16(newBuffer[ptr:], uint16(loginOpcode)) ptr += 2 } else { binary.BigEndian.PutUint16(newBuffer[ptr:], uint16(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 { if opcodeSize == 0 { opcodeSize = DefaultOpcodeSize } app := &AppPacket{ Packet: NewPacket(0, p.Buffer), opcodeSize: opcodeSize, } 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:] } return app }