1
0
Protocol/packets/protopacket.go

392 lines
8.7 KiB
Go

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
}