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) 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" "encoding/binary"
"time" "time"
"git.sharkk.net/EQ2/Protocol/crypto"
"git.sharkk.net/EQ2/Protocol/opcodes" "git.sharkk.net/EQ2/Protocol/opcodes"
) )
@ -31,6 +32,9 @@ type ProtoPacket struct {
// Compression/encoding settings // Compression/encoding settings
CompressThreshold int CompressThreshold int
EncodeKey int EncodeKey int
// Client version for protocol compatibility
clientVersion int16
} }
const DefaultCompressThreshold = 100 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) // NewProtoPacketFromRaw creates from raw buffer (matches EQProtocolPacket constructor)
func NewProtoPacketFromRaw(buf []byte, opcodeOverride int, manager opcodes.Manager) *ProtoPacket { func NewProtoPacketFromRaw(buf []byte, opcodeOverride int, manager opcodes.Manager) *ProtoPacket {
var offset uint32 var offset uint32
@ -79,8 +93,29 @@ func NewProtoPacketFromRaw(buf []byte, opcodeOverride int, manager opcodes.Manag
return pp 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) // 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 { if p.packetPrepared {
return 0 return 0
} }
@ -93,60 +128,48 @@ func (p *ProtoPacket) PreparePacket(maxLen int16) int8 {
// Convert emulator opcode to network opcode // Convert emulator opcode to network opcode
loginOpcode := p.manager.EmuToEQ(p.LoginOp) loginOpcode := p.manager.EmuToEQ(p.LoginOp)
if loginOpcode == 0xcdcd { // Invalid opcode
// Apply compression if needed return -1
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 // 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 var offset int8
newSize := len(p.Buffer) + 2 // Base size with sequence newSize := len(p.Buffer) + 2 + 1 // seq(2) + compress_flag(1)
// Add compression flag space if compressed
if p.eq2Compressed {
newSize++
}
// Handle opcode encoding (matches C++ implementation)
oversized := false oversized := false
// Handle different opcode sizes and formats
if loginOpcode != 2 { // Not OP_SessionResponse if loginOpcode != 2 { // Not OP_SessionResponse
newSize++ // for opcode type byte
if loginOpcode >= 255 { if loginOpcode >= 255 {
newSize += 3 // oversized opcode (0xFF + 2 bytes) newSize += 2 // oversized opcode needs extra 2 bytes
oversized = true oversized = true
} else { } 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 // Build new buffer
newBuffer := make([]byte, newSize) 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 // Space for compress flag (will be 0 for uncompressed)
if p.eq2Compressed { ptr++ // This is at position 2, will be overwritten if compressed
newBuffer[ptr] = 0x5a // EQ2 compression flag
ptr++
}
// Encode opcode
if loginOpcode != 2 { if loginOpcode != 2 {
if oversized { if oversized {
newBuffer[ptr] = 0xff // Oversized marker newBuffer[ptr] = 0xff // Oversized marker
ptr++ 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 { } else {
// OP_SessionResponse: just 1 byte
newBuffer[ptr] = byte(loginOpcode) newBuffer[ptr] = byte(loginOpcode)
ptr++ ptr++
} }
@ -154,8 +177,9 @@ func (p *ProtoPacket) PreparePacket(maxLen int16) int8 {
// Copy original packet data // Copy original packet data
copy(newBuffer[ptr:], p.Buffer) copy(newBuffer[ptr:], p.Buffer)
// Replace buffer
p.Buffer = newBuffer p.Buffer = newBuffer
offset = int8(ptr - 2) // Return offset past sequence field offset = int8(newSize - len(p.Buffer) - 1)
return offset return offset
} }
@ -165,6 +189,60 @@ func (p *ProtoPacket) IsCompressed() bool {
return p.eq2Compressed 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 // Serialize writes the protocol packet to a destination buffer
func (p *ProtoPacket) Serialize(dest []byte, offset int8) uint32 { func (p *ProtoPacket) Serialize(dest []byte, offset int8) uint32 {
pos := 0 pos := 0

View File

@ -58,6 +58,7 @@ type Stream struct {
// Opcode management // Opcode management
opcodeManager opcodes.Manager opcodeManager opcodes.Manager
opcodeSize uint8 opcodeSize uint8
clientVersion int16
// Cipher for encryption // Cipher for encryption
cipher *crypto.Ciphers cipher *crypto.Ciphers
@ -258,13 +259,13 @@ func (s *Stream) SendPacket(app *packets.AppPacket) error {
return fmt.Errorf("stream not established") return fmt.Errorf("stream not established")
} }
// Convert to protocol packet // Convert to EQ2 protocol packet
proto := app.ToProto() proto := packets.NewEQ2Packet(app.GetOpcode(), app.Buffer, s.opcodeManager)
proto.CompressThreshold = 100 proto.SetVersion(s.clientVersion)
proto.EncodeKey = s.encodeKey
// Check if packet needs fragmentation // Check if packet needs fragmentation BEFORE preparation
if proto.Size() > uint32(s.maxLen-8) { // 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) return s.sendFragmented(proto)
} }
@ -332,7 +333,7 @@ func (s *Stream) processQueues() error {
proto := pending.packet proto := pending.packet
var encryptOffset int8 var encryptOffset int8
if proto.LoginOp != opcodes.OP_Unknown { if proto.LoginOp != opcodes.OP_Unknown {
encryptOffset = proto.PreparePacket(int16(s.maxLen)) encryptOffset = proto.PreparePacket(s.maxLen)
} }
data := make([]byte, len(proto.Buffer)+2) data := make([]byte, len(proto.Buffer)+2)
@ -395,7 +396,7 @@ func (s *Stream) processQueues() error {
// Prepare unreliable packets too // Prepare unreliable packets too
var encryptOffset int8 var encryptOffset int8
if proto.LoginOp != opcodes.OP_Unknown { if proto.LoginOp != opcodes.OP_Unknown {
encryptOffset = proto.PreparePacket(int16(s.maxLen)) encryptOffset = proto.PreparePacket(s.maxLen)
} }
data := make([]byte, len(proto.Buffer)) data := make([]byte, len(proto.Buffer))
@ -430,7 +431,7 @@ func (s *Stream) processQueues() error {
// sendFragmented sends a fragmented packet (matches EQStream::SendPacket) // sendFragmented sends a fragmented packet (matches EQStream::SendPacket)
func (s *Stream) sendFragmented(proto *packets.ProtoPacket) error { func (s *Stream) sendFragmented(proto *packets.ProtoPacket) error {
// Prepare packet first // Prepare packet first
encryptOffset := proto.PreparePacket(int16(s.maxLen)) encryptOffset := proto.PreparePacket(s.maxLen)
if encryptOffset < 0 { if encryptOffset < 0 {
return fmt.Errorf("failed to prepare packet") 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) 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 { 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() s.mu.Lock()
seq := s.seqOut 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.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{ pending := &pendingPacket{
packet: proto.Copy(), packet: proto.Copy(),
@ -557,12 +550,29 @@ func (s *Stream) sendReliable(proto *packets.ProtoPacket) error {
attempts: 1, attempts: 1,
nextRetry: time.Now().Add(s.rto), nextRetry: time.Now().Add(s.rto),
} }
s.mu.Lock()
s.pendingAcks[seq] = pending s.pendingAcks[seq] = pending
s.mu.Unlock() 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 // Helper methods