387 lines
8.6 KiB
Go
387 lines
8.6 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
|
|
}
|
|
|
|
// 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
|
|
}
|