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)
|
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"
|
"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
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user