1
0
Protocol/packets/protopacket.go

420 lines
9.2 KiB
Go

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
}