1
0

fix packet building order

This commit is contained in:
Sky Johnson 2025-09-03 14:19:51 -05:00
parent 50d4d9d6f0
commit fd2ebfd6cc
3 changed files with 172 additions and 79 deletions

View File

@ -55,3 +55,8 @@ func (c *Ciphers) Encrypt(data []byte) {
c.server.XORKeyStream(data, data)
}
}
// IsEncrypted returns true if encryption is active
func (c *Ciphers) IsEncrypted() bool {
return c != nil && c.server != nil
}

View File

@ -4,6 +4,7 @@ import (
"encoding/binary"
"time"
"git.sharkk.net/EQ2/Protocol/crypto"
"git.sharkk.net/EQ2/Protocol/opcodes"
)
@ -31,6 +32,9 @@ type ProtoPacket struct {
// Compression/encoding settings
CompressThreshold int
EncodeKey int
// Client version for protocol compatibility
clientVersion int16
}
const DefaultCompressThreshold = 100
@ -44,6 +48,16 @@ func NewProtoPacket(op uint16, buf []byte, manager opcodes.Manager) *ProtoPacket
}
}
// 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
@ -79,8 +93,29 @@ func NewProtoPacketFromRaw(buf []byte, opcodeOverride int, manager opcodes.Manag
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 int16) int8 {
func (p *ProtoPacket) PreparePacket(maxLen uint16) int8 {
if p.packetPrepared {
return 0
}
@ -93,60 +128,48 @@ func (p *ProtoPacket) PreparePacket(maxLen int16) int8 {
// 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
}
if loginOpcode == 0xcdcd { // Invalid opcode
return -1
}
// Build new packet buffer with proper headers
// 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 // Base size with sequence
// Add compression flag space if compressed
if p.eq2Compressed {
newSize++
}
// Handle opcode encoding (matches C++ implementation)
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 += 3 // oversized opcode (0xFF + 2 bytes)
newSize += 2 // oversized opcode needs extra 2 bytes
oversized = true
} else {
newSize += 2 // normal opcode
// Swap bytes for network order
loginOpcode = (loginOpcode>>8)&0xFF | (loginOpcode<<8)&0xFF00
}
} else {
newSize++ // single byte for OP_SessionResponse
}
// Build new buffer
newBuffer := make([]byte, newSize)
ptr := 2 // Skip sequence field (filled by stream)
// Leave first 2 bytes for sequence (filled by SequencedPush)
ptr := 2
// Add compression flag
if p.eq2Compressed {
newBuffer[ptr] = 0x5a // EQ2 compression flag
ptr++
}
// Space for compress flag (will be 0 for uncompressed)
ptr++ // This is at position 2, will be overwritten if compressed
// 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
}
// Write opcode as 2 bytes
binary.BigEndian.PutUint16(newBuffer[ptr:], loginOpcode)
ptr += 2
} else {
// OP_SessionResponse: just 1 byte
newBuffer[ptr] = byte(loginOpcode)
ptr++
}
@ -154,8 +177,9 @@ func (p *ProtoPacket) PreparePacket(maxLen int16) int8 {
// Copy original packet data
copy(newBuffer[ptr:], p.Buffer)
// Replace buffer
p.Buffer = newBuffer
offset = int8(ptr - 2) // Return offset past sequence field
offset = int8(newSize - len(p.Buffer) - 1)
return offset
}
@ -165,6 +189,60 @@ 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

View File

@ -58,6 +58,7 @@ type Stream struct {
// Opcode management
opcodeManager opcodes.Manager
opcodeSize uint8
clientVersion int16
// Cipher for encryption
cipher *crypto.Ciphers
@ -258,13 +259,13 @@ func (s *Stream) SendPacket(app *packets.AppPacket) error {
return fmt.Errorf("stream not established")
}
// Convert to protocol packet
proto := app.ToProto()
proto.CompressThreshold = 100
proto.EncodeKey = s.encodeKey
// Convert to EQ2 protocol packet
proto := packets.NewEQ2Packet(app.GetOpcode(), app.Buffer, s.opcodeManager)
proto.SetVersion(s.clientVersion)
// Check if packet needs fragmentation
if proto.Size() > uint32(s.maxLen-8) {
// Check if packet needs fragmentation BEFORE preparation
// C++ checks size > (MaxLen - 8) for: proto-op(2), seq(2), app-op(2) ... data ... crc(2)
if len(app.Buffer) > int(s.maxLen-8) {
return s.sendFragmented(proto)
}
@ -332,7 +333,7 @@ func (s *Stream) processQueues() error {
proto := pending.packet
var encryptOffset int8
if proto.LoginOp != opcodes.OP_Unknown {
encryptOffset = proto.PreparePacket(int16(s.maxLen))
encryptOffset = proto.PreparePacket(s.maxLen)
}
data := make([]byte, len(proto.Buffer)+2)
@ -395,7 +396,7 @@ func (s *Stream) processQueues() error {
// Prepare unreliable packets too
var encryptOffset int8
if proto.LoginOp != opcodes.OP_Unknown {
encryptOffset = proto.PreparePacket(int16(s.maxLen))
encryptOffset = proto.PreparePacket(s.maxLen)
}
data := make([]byte, len(proto.Buffer))
@ -430,7 +431,7 @@ func (s *Stream) processQueues() error {
// sendFragmented sends a fragmented packet (matches EQStream::SendPacket)
func (s *Stream) sendFragmented(proto *packets.ProtoPacket) error {
// Prepare packet first
encryptOffset := proto.PreparePacket(int16(s.maxLen))
encryptOffset := proto.PreparePacket(s.maxLen)
if encryptOffset < 0 {
return fmt.Errorf("failed to prepare packet")
}
@ -513,42 +514,34 @@ func (s *Stream) sendReliableFragment(proto *packets.ProtoPacket) error {
return s.sendRawWithCRC(opcodes.OP_Packet, data)
}
// sendReliable sends a packet reliably with sequencing
// sendReliable sends a packet reliably with sequencing (matches C++ flow)
func (s *Stream) sendReliable(proto *packets.ProtoPacket) error {
// Prepare packet (matches C++ PreparePacket)
if !proto.IsPrepared() {
// C++ checks (app->PreparePacket(MaxLen) == 255) but PreparePacket returns int8(-1)
// Since -1 != 255 as int8, this check would never trigger in C++
// For 1:1 compatibility, we don't check the return value
proto.PreparePacket(s.maxLen)
}
// Compress if needed (matches C++ EQ2_Compress)
compressedOffset := int8(0)
if !proto.IsCompressed() && proto.Size() > 128 {
compressedOffset = proto.EQ2Compress(3) // Default offset 3
}
// Encrypt if needed (matches C++ EncryptPacket)
if s.cipher != nil && !proto.IsEncrypted() {
proto.EncryptPacket(s.cipher, compressedOffset, 0)
}
// Assign sequence number (matches C++ SequencedPush)
s.mu.Lock()
seq := s.seqOut
// Set sequence in packet buffer (first 2 bytes)
binary.BigEndian.PutUint16(proto.Buffer[:2], seq)
proto.SetSequence(int32(seq))
s.seqOut = s.incrementSequence(s.seqOut)
s.mu.Unlock()
// Prepare packet first (adds headers, compression)
var encryptOffset int8
if proto.LoginOp != opcodes.OP_Unknown {
encryptOffset = proto.PreparePacket(int16(s.maxLen))
if encryptOffset < 0 {
return fmt.Errorf("failed to prepare packet")
}
}
// Build packet with sequence
data := make([]byte, len(proto.Buffer)+2)
binary.BigEndian.PutUint16(data[:2], seq)
copy(data[2:], proto.Buffer)
// Apply encryption if needed (BEFORE CRC, with proper offset)
if s.cipher != nil && len(data) > 2 {
if proto.IsCompressed() {
// Compressed packet: encrypt from compress offset (typically 3)
if len(data) > 3 {
s.cipher.Encrypt(data[3:]) // Skip seq[2] + compress_flag[1]
}
} else {
// Uncompressed packet: encrypt from 2 + offset
offset := 2 + int(encryptOffset)
if len(data) > offset {
s.cipher.Encrypt(data[offset:])
}
}
}
pending := &pendingPacket{
packet: proto.Copy(),
@ -557,12 +550,29 @@ func (s *Stream) sendReliable(proto *packets.ProtoPacket) error {
attempts: 1,
nextRetry: time.Now().Add(s.rto),
}
s.mu.Lock()
s.pendingAcks[seq] = pending
s.mu.Unlock()
return s.sendRawWithCRC(opcodes.OP_Packet, data)
// Send via WritePacket equivalent
return s.writePacket(proto)
}
// writePacket sends a protocol packet (matches C++ WritePacket)
func (s *Stream) writePacket(p *packets.ProtoPacket) error {
// Serialize packet
data := make([]byte, p.Size()+2)
length := p.Serialize(data, 0)
// Add CRC for non-session packets
if p.Opcode != opcodes.OP_SessionRequest && p.Opcode != opcodes.OP_SessionResponse {
data = data[:length]
data = packets.AppendCRC(data, s.crcKey)
}
atomic.AddUint64(&s.packetsOut, 1)
atomic.AddUint64(&s.bytesOut, uint64(len(data)))
return s.conn.AsyncWrite(data, nil)
}
// Helper methods