fix packet building order
This commit is contained in:
parent
50d4d9d6f0
commit
fd2ebfd6cc
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user