1
0
Protocol/packets/protopacket.go

470 lines
11 KiB
Go

package packets
import (
"encoding/binary"
"time"
"git.sharkk.net/EQ2/Protocol/crypto"
"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
// Client version for protocol compatibility
clientVersion int16
}
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,
}
}
// NewEQ2Packet creates an EQ2 protocol packet (matches EQ2Packet constructor)
func NewEQ2Packet(loginOp opcodes.EmuOpcode, buf []byte, manager opcodes.Manager) *ProtoPacket {
return &ProtoPacket{
Packet: NewPacket(opcodes.OP_Packet, buf),
LoginOp: loginOp,
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
}
// SetVersion sets the client version
func (p *ProtoPacket) SetVersion(version int16) {
p.clientVersion = version
p.Version = version
}
// SetSequence sets the packet sequence number
func (p *ProtoPacket) SetSequence(seq int32) {
p.Sequence = seq
}
// IsPrepared returns whether packet has been prepared
func (p *ProtoPacket) IsPrepared() bool {
return p.packetPrepared
}
// IsEncrypted returns whether packet is encrypted
func (p *ProtoPacket) IsEncrypted() bool {
return p.packetEncrypted
}
// PreparePacket prepares an EQ2 packet for transmission (matches EQ2Packet::PreparePacket)
func (p *ProtoPacket) PreparePacket(maxLen uint16) 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)
if loginOpcode == 0xcdcd { // Invalid opcode
return -1
}
// NOTE: Compression is NOT done here in C++, it's done separately
// via EQ2_Compress after PreparePacket
// Calculate new size: sequence(2) + compressed_flag(1) + opcode + data
var offset int8
newSize := len(p.Buffer) + 2 + 1 // seq(2) + compress_flag(1)
oversized := false
// Handle different opcode sizes and formats
if loginOpcode != 2 { // Not OP_SessionResponse
newSize++ // for opcode type byte
if loginOpcode >= 255 {
newSize += 2 // oversized opcode needs extra 2 bytes
oversized = true
} else {
// Swap bytes for network order
loginOpcode = (loginOpcode>>8)&0xFF | (loginOpcode<<8)&0xFF00
}
}
// Build new buffer
newBuffer := make([]byte, newSize)
// Leave first 2 bytes for sequence (filled by SequencedPush)
ptr := 2
// Space for compress flag (will be 0 for uncompressed)
ptr++ // This is at position 2, will be overwritten if compressed
if loginOpcode != 2 {
if oversized {
newBuffer[ptr] = 0xff // Oversized marker
ptr++
}
// Write opcode as 2 bytes
binary.BigEndian.PutUint16(newBuffer[ptr:], loginOpcode)
ptr += 2
} else {
// OP_SessionResponse: just 1 byte
newBuffer[ptr] = byte(loginOpcode)
ptr++
}
// Copy original packet data
copy(newBuffer[ptr:], p.Buffer)
// Replace buffer
p.Buffer = newBuffer
offset = int8(newSize - len(p.Buffer) - 1)
return offset
}
// IsCompressed returns whether the packet is compressed
func (p *ProtoPacket) IsCompressed() bool {
return p.eq2Compressed
}
// EQ2Compress compresses packet data (matches EQStream::EQ2_Compress)
func (p *ProtoPacket) EQ2Compress(offset int8) int8 {
if offset <= 0 || int(offset) >= len(p.Buffer) {
return 0
}
// Compress data from offset onwards
dataToCompress := p.Buffer[offset:]
compressed, err := Compress(dataToCompress)
if err != nil || len(compressed) >= len(dataToCompress) {
return 0 // Compression failed or didn't reduce size
}
// Rebuild buffer with compression flag
newSize := int(offset) + len(compressed)
newBuffer := make([]byte, newSize)
// Copy header bytes before offset
copy(newBuffer[:offset], p.Buffer[:offset])
// Set compression flag at offset-1
newBuffer[offset-1] = 1 // Compression flag
// Copy compressed data
copy(newBuffer[offset:], compressed)
p.Buffer = newBuffer
p.eq2Compressed = true
return offset - 1 // Return compression flag position
}
// EncryptPacket encrypts packet data (matches EQStream::EncryptPacket)
func (p *ProtoPacket) EncryptPacket(cipher *crypto.Ciphers, compressOffset int8, offset int8) {
if cipher == nil || !cipher.IsEncrypted() || len(p.Buffer) <= 2 {
return
}
p.packetEncrypted = true
if p.eq2Compressed && compressOffset > 0 {
// Encrypted compressed data from compress offset
if int(compressOffset) < len(p.Buffer) {
cipher.Encrypt(p.Buffer[compressOffset:])
}
} else {
// Encrypt uncompressed data from 2 + offset
startPos := 2 + int(offset)
if startPos < len(p.Buffer) {
cipher.Encrypt(p.Buffer[startPos:])
}
}
}
// 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
}