compat pass 3
This commit is contained in:
parent
c692b25ac0
commit
d70daa98be
@ -9,8 +9,7 @@ import (
|
|||||||
// DefaultOpcodeSize is the default opcode size for application packets
|
// DefaultOpcodeSize is the default opcode size for application packets
|
||||||
var DefaultOpcodeSize uint8 = 2
|
var DefaultOpcodeSize uint8 = 2
|
||||||
|
|
||||||
// AppPacket handles high-level game opcodes and application data
|
// AppPacket handles high-level game opcodes (matches EQApplicationPacket)
|
||||||
// This is the main packet type used by game logic
|
|
||||||
type AppPacket struct {
|
type AppPacket struct {
|
||||||
*Packet
|
*Packet
|
||||||
|
|
||||||
@ -29,7 +28,7 @@ func NewAppPacket(manager opcodes.Manager) *AppPacket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppPacketWithOp creates a new application packet with opcode
|
// NewAppPacketWithOp creates with opcode
|
||||||
func NewAppPacketWithOp(op opcodes.EmuOpcode, manager opcodes.Manager) *AppPacket {
|
func NewAppPacketWithOp(op opcodes.EmuOpcode, manager opcodes.Manager) *AppPacket {
|
||||||
app := &AppPacket{
|
app := &AppPacket{
|
||||||
Packet: NewPacket(0, nil),
|
Packet: NewPacket(0, nil),
|
||||||
@ -40,7 +39,7 @@ func NewAppPacketWithOp(op opcodes.EmuOpcode, manager opcodes.Manager) *AppPacke
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppPacketWithSize creates a new application packet with opcode and size
|
// NewAppPacketWithSize creates with opcode and size
|
||||||
func NewAppPacketWithSize(op opcodes.EmuOpcode, size uint32, manager opcodes.Manager) *AppPacket {
|
func NewAppPacketWithSize(op opcodes.EmuOpcode, size uint32, manager opcodes.Manager) *AppPacket {
|
||||||
app := &AppPacket{
|
app := &AppPacket{
|
||||||
Packet: NewPacket(0, make([]byte, size)),
|
Packet: NewPacket(0, make([]byte, size)),
|
||||||
@ -51,7 +50,7 @@ func NewAppPacketWithSize(op opcodes.EmuOpcode, size uint32, manager opcodes.Man
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppPacketWithData creates a new application packet with opcode and data
|
// NewAppPacketWithData creates with opcode and data
|
||||||
func NewAppPacketWithData(op opcodes.EmuOpcode, data []byte, manager opcodes.Manager) *AppPacket {
|
func NewAppPacketWithData(op opcodes.EmuOpcode, data []byte, manager opcodes.Manager) *AppPacket {
|
||||||
app := &AppPacket{
|
app := &AppPacket{
|
||||||
Packet: NewPacket(0, data),
|
Packet: NewPacket(0, data),
|
||||||
@ -62,8 +61,7 @@ func NewAppPacketWithData(op opcodes.EmuOpcode, data []byte, manager opcodes.Man
|
|||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppPacketFromRaw creates app packet from raw buffer (used by ProtoPacket)
|
// NewAppPacketFromRaw creates from raw buffer (matches EQApplicationPacket constructor)
|
||||||
// Assumes first bytes are opcode based on opcodeSize
|
|
||||||
func NewAppPacketFromRaw(buf []byte, opcodeSize uint8, manager opcodes.Manager) *AppPacket {
|
func NewAppPacketFromRaw(buf []byte, opcodeSize uint8, manager opcodes.Manager) *AppPacket {
|
||||||
if opcodeSize == 0 {
|
if opcodeSize == 0 {
|
||||||
opcodeSize = DefaultOpcodeSize
|
opcodeSize = DefaultOpcodeSize
|
||||||
@ -75,23 +73,28 @@ func NewAppPacketFromRaw(buf []byte, opcodeSize uint8, manager opcodes.Manager)
|
|||||||
manager: manager,
|
manager: manager,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
offset := 0
|
||||||
|
|
||||||
|
// Extract opcode based on size
|
||||||
if opcodeSize == 1 && len(buf) >= 1 {
|
if opcodeSize == 1 && len(buf) >= 1 {
|
||||||
app.Opcode = uint16(buf[0])
|
app.Opcode = uint16(buf[0])
|
||||||
if len(buf) > 1 {
|
offset = 1
|
||||||
app.Buffer = make([]byte, len(buf)-1)
|
|
||||||
copy(app.Buffer, buf[1:])
|
|
||||||
}
|
|
||||||
} else if len(buf) >= 2 {
|
} else if len(buf) >= 2 {
|
||||||
app.Opcode = binary.BigEndian.Uint16(buf[:2])
|
app.Opcode = binary.BigEndian.Uint16(buf[:2])
|
||||||
if len(buf) > 2 {
|
offset = 2
|
||||||
app.Buffer = make([]byte, len(buf)-2)
|
}
|
||||||
copy(app.Buffer, buf[2:])
|
|
||||||
}
|
// Copy remaining data as payload
|
||||||
|
if len(buf) > offset {
|
||||||
|
app.Buffer = make([]byte, len(buf)-offset)
|
||||||
|
copy(app.Buffer, buf[offset:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert EQ opcode to emulator opcode
|
// Convert EQ opcode to emulator opcode
|
||||||
if app.manager != nil {
|
if app.manager != nil {
|
||||||
app.emuOpcode = app.manager.EQToEmu(app.Opcode)
|
app.emuOpcode = app.manager.EQToEmu(app.Opcode)
|
||||||
|
} else {
|
||||||
|
app.emuOpcode = opcodes.EmuOpcode(app.Opcode)
|
||||||
}
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
@ -99,21 +102,12 @@ func NewAppPacketFromRaw(buf []byte, opcodeSize uint8, manager opcodes.Manager)
|
|||||||
|
|
||||||
// Size returns total packet size including opcode
|
// Size returns total packet size including opcode
|
||||||
func (a *AppPacket) Size() uint32 {
|
func (a *AppPacket) Size() uint32 {
|
||||||
return uint32(len(a.Buffer)) + uint32(a.opcodeSize)
|
// Handle special encoding where low byte = 0x00 needs extra byte
|
||||||
}
|
extraBytes := uint32(0)
|
||||||
|
if a.opcodeSize == 2 && (a.Opcode&0x00ff) == 0 {
|
||||||
// SetOpcodeSize sets the opcode size
|
extraBytes = 1
|
||||||
func (a *AppPacket) SetOpcodeSize(size uint8) {
|
|
||||||
a.opcodeSize = size
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetManager sets the opcode manager for translation
|
|
||||||
func (a *AppPacket) SetManager(manager opcodes.Manager) {
|
|
||||||
a.manager = manager
|
|
||||||
// Re-translate if we have an opcode
|
|
||||||
if a.emuOpcode != opcodes.OP_Unknown && a.manager != nil {
|
|
||||||
a.Opcode = a.manager.EmuToEQ(a.emuOpcode)
|
|
||||||
}
|
}
|
||||||
|
return uint32(len(a.Buffer)) + uint32(a.opcodeSize) + extraBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOpcode sets the emulator opcode and translates to EQ opcode
|
// SetOpcode sets the emulator opcode and translates to EQ opcode
|
||||||
@ -122,15 +116,13 @@ func (a *AppPacket) SetOpcode(op opcodes.EmuOpcode) {
|
|||||||
if a.manager != nil {
|
if a.manager != nil {
|
||||||
a.Opcode = a.manager.EmuToEQ(op)
|
a.Opcode = a.manager.EmuToEQ(op)
|
||||||
} else {
|
} else {
|
||||||
// Fallback to direct assignment if no manager
|
|
||||||
a.Opcode = uint16(op)
|
a.Opcode = uint16(op)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOpcode returns the emulator opcode (with caching)
|
// GetOpcode returns the emulator opcode
|
||||||
func (a *AppPacket) GetOpcode() opcodes.EmuOpcode {
|
func (a *AppPacket) GetOpcode() opcodes.EmuOpcode {
|
||||||
if a.emuOpcode == opcodes.OP_Unknown && a.manager != nil {
|
if a.emuOpcode == opcodes.OP_Unknown && a.manager != nil {
|
||||||
// Convert from protocol opcode
|
|
||||||
a.emuOpcode = a.manager.EQToEmu(a.Opcode)
|
a.emuOpcode = a.manager.EQToEmu(a.Opcode)
|
||||||
}
|
}
|
||||||
return a.emuOpcode
|
return a.emuOpcode
|
||||||
@ -144,20 +136,16 @@ func (a *AppPacket) GetOpcodeName() string {
|
|||||||
return "OP_Unknown"
|
return "OP_Unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy creates a deep copy of this application packet
|
// SetManager sets the opcode manager
|
||||||
func (a *AppPacket) Copy() *AppPacket {
|
func (a *AppPacket) SetManager(manager opcodes.Manager) {
|
||||||
newApp := &AppPacket{
|
a.manager = manager
|
||||||
Packet: NewPacket(a.Opcode, a.Buffer),
|
// Re-translate if we have an opcode
|
||||||
emuOpcode: a.emuOpcode,
|
if a.emuOpcode != opcodes.OP_Unknown && a.manager != nil {
|
||||||
opcodeSize: a.opcodeSize,
|
a.Opcode = a.manager.EmuToEQ(a.emuOpcode)
|
||||||
manager: a.manager,
|
|
||||||
}
|
}
|
||||||
newApp.Packet.CopyInfo(a.Packet)
|
|
||||||
return newApp
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize writes the application packet to a destination buffer
|
// Serialize writes to destination (matches EQApplicationPacket::serialize)
|
||||||
// Handles special opcode encoding rules for application-level packets
|
|
||||||
func (a *AppPacket) Serialize(dest []byte) uint32 {
|
func (a *AppPacket) Serialize(dest []byte) uint32 {
|
||||||
opcodeBytes := a.opcodeSize
|
opcodeBytes := a.opcodeSize
|
||||||
pos := 0
|
pos := 0
|
||||||
@ -187,84 +175,34 @@ func (a *AppPacket) Serialize(dest []byte) uint32 {
|
|||||||
return uint32(len(a.Buffer)) + uint32(opcodeBytes)
|
return uint32(len(a.Buffer)) + uint32(opcodeBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combine combines this packet with another application packet
|
// Combine combines with another app packet (matches EQApplicationPacket::combine)
|
||||||
func (a *AppPacket) Combine(rhs *AppPacket) bool {
|
func (a *AppPacket) Combine(rhs *AppPacket) bool {
|
||||||
const opAppCombined = 0x19 // OP_AppCombined
|
// Application packet combining is not implemented in original
|
||||||
|
// Use protocol-level combining instead
|
||||||
// Check if we can combine these packets
|
return false
|
||||||
totalSize := a.Size() + rhs.Size()
|
}
|
||||||
if totalSize > 512 { // Reasonable max combined packet size
|
|
||||||
return false
|
// Copy creates a deep copy
|
||||||
}
|
func (a *AppPacket) Copy() *AppPacket {
|
||||||
|
newApp := &AppPacket{
|
||||||
// If this isn't already a combined packet, convert it
|
Packet: NewPacket(a.Opcode, a.Buffer),
|
||||||
if a.Opcode != opAppCombined {
|
emuOpcode: a.emuOpcode,
|
||||||
// Create combined packet structure
|
opcodeSize: a.opcodeSize,
|
||||||
oldSize := a.Size()
|
manager: a.manager,
|
||||||
newSize := oldSize + rhs.Size() + 2 // +2 for size headers
|
}
|
||||||
|
newApp.Packet.CopyInfo(a.Packet)
|
||||||
newBuffer := make([]byte, newSize)
|
return newApp
|
||||||
pos := 0
|
}
|
||||||
|
|
||||||
// Write first packet size and data
|
// ToProto converts to protocol packet
|
||||||
newBuffer[pos] = byte(oldSize)
|
func (a *AppPacket) ToProto() *ProtoPacket {
|
||||||
pos++
|
// Serialize the application packet
|
||||||
|
tmpBuf := make([]byte, a.Size())
|
||||||
// Serialize first packet
|
a.Serialize(tmpBuf)
|
||||||
tmpBuf := make([]byte, oldSize)
|
|
||||||
a.Serialize(tmpBuf)
|
proto := NewProtoPacket(opcodes.OP_Packet, tmpBuf, a.manager)
|
||||||
copy(newBuffer[pos:], tmpBuf)
|
proto.LoginOp = a.emuOpcode
|
||||||
pos += int(oldSize)
|
proto.CopyInfo(a.Packet)
|
||||||
|
|
||||||
// Write second packet size and data
|
return proto
|
||||||
rhsSize := rhs.Size()
|
|
||||||
newBuffer[pos] = byte(rhsSize)
|
|
||||||
pos++
|
|
||||||
|
|
||||||
// Serialize second packet
|
|
||||||
tmpBuf = make([]byte, rhsSize)
|
|
||||||
rhs.Serialize(tmpBuf)
|
|
||||||
copy(newBuffer[pos:], tmpBuf)
|
|
||||||
|
|
||||||
// Update this packet to be a combined packet
|
|
||||||
a.Opcode = opAppCombined
|
|
||||||
a.Buffer = newBuffer
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is already a combined packet, add to it
|
|
||||||
rhsSize := rhs.Size()
|
|
||||||
if rhsSize >= 255 {
|
|
||||||
// Oversized packet handling
|
|
||||||
newBuffer := make([]byte, len(a.Buffer)+int(rhsSize)+3)
|
|
||||||
copy(newBuffer, a.Buffer)
|
|
||||||
pos := len(a.Buffer)
|
|
||||||
|
|
||||||
newBuffer[pos] = 255 // Oversized marker
|
|
||||||
pos++
|
|
||||||
binary.BigEndian.PutUint16(newBuffer[pos:], uint16(rhsSize))
|
|
||||||
pos += 2
|
|
||||||
|
|
||||||
tmpBuf := make([]byte, rhsSize)
|
|
||||||
rhs.Serialize(tmpBuf)
|
|
||||||
copy(newBuffer[pos:], tmpBuf)
|
|
||||||
|
|
||||||
a.Buffer = newBuffer
|
|
||||||
} else {
|
|
||||||
// Normal sized addition
|
|
||||||
newBuffer := make([]byte, len(a.Buffer)+int(rhsSize)+1)
|
|
||||||
copy(newBuffer, a.Buffer)
|
|
||||||
pos := len(a.Buffer)
|
|
||||||
|
|
||||||
newBuffer[pos] = byte(rhsSize)
|
|
||||||
pos++
|
|
||||||
|
|
||||||
tmpBuf := make([]byte, rhsSize)
|
|
||||||
rhs.Serialize(tmpBuf)
|
|
||||||
copy(newBuffer[pos:], tmpBuf)
|
|
||||||
|
|
||||||
a.Buffer = newBuffer
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
@ -5,33 +5,42 @@ import (
|
|||||||
"compress/zlib"
|
"compress/zlib"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"hash/crc32"
|
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"git.sharkk.net/EQ2/Protocol/crypto"
|
"git.sharkk.net/EQ2/Protocol/crypto"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ValidateCRC validates packet CRC using EQ's CRC32 implementation
|
// ValidateCRC validates packet CRC using EQ2's custom CRC16
|
||||||
func ValidateCRC(buffer []byte, key uint32) bool {
|
func ValidateCRC(buffer []byte, key uint32) bool {
|
||||||
if len(buffer) < 4 {
|
if len(buffer) < 2 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract CRC from last 4 bytes
|
// Extract CRC from last 2 bytes (EQ2 uses CRC16)
|
||||||
packetCRC := binary.BigEndian.Uint32(buffer[len(buffer)-4:])
|
packetCRC := binary.BigEndian.Uint16(buffer[len(buffer)-2:])
|
||||||
|
|
||||||
// Calculate CRC on data portion (excluding CRC bytes)
|
// Calculate CRC on data portion (excluding CRC bytes)
|
||||||
data := buffer[:len(buffer)-4]
|
data := buffer[:len(buffer)-2]
|
||||||
calculatedCRC := CalculateCRC(data, key)
|
calculatedCRC := crypto.CalculateCRC(data, key)
|
||||||
|
|
||||||
return packetCRC == calculatedCRC
|
return packetCRC == calculatedCRC
|
||||||
}
|
}
|
||||||
|
|
||||||
// CalculateCRC calculates CRC32 for packet data
|
// AppendCRC appends CRC16 to packet buffer using EQ2's custom CRC
|
||||||
func CalculateCRC(data []byte, key uint32) uint32 {
|
func AppendCRC(buffer []byte, key uint32) []byte {
|
||||||
// EQ uses standard CRC32 with XOR key
|
crc := crypto.CalculateCRC(buffer, key)
|
||||||
crc := crc32.ChecksumIEEE(data)
|
result := make([]byte, len(buffer)+2)
|
||||||
return crc ^ key
|
copy(result, buffer)
|
||||||
|
binary.BigEndian.PutUint16(result[len(buffer):], crc)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// StripCRC removes CRC16 from packet buffer
|
||||||
|
func StripCRC(buffer []byte) []byte {
|
||||||
|
if len(buffer) < 2 {
|
||||||
|
return buffer
|
||||||
|
}
|
||||||
|
return buffer[:len(buffer)-2]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compress compresses packet data using zlib (matches EQ compression)
|
// Compress compresses packet data using zlib (matches EQ compression)
|
||||||
@ -42,16 +51,13 @@ func Compress(src []byte) ([]byte, error) {
|
|||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
// EQ uses default compression level
|
|
||||||
w := zlib.NewWriter(&buf)
|
|
||||||
|
|
||||||
// Write uncompressed length first (4 bytes) - EQ protocol requirement
|
// Write uncompressed length first (4 bytes) - EQ protocol requirement
|
||||||
uncompressedLen := uint32(len(src))
|
if err := binary.Write(&buf, binary.BigEndian, uint32(len(src))); err != nil {
|
||||||
if err := binary.Write(&buf, binary.BigEndian, uncompressedLen); err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compress the data
|
// Compress the data
|
||||||
|
w := zlib.NewWriter(&buf)
|
||||||
if _, err := w.Write(src); err != nil {
|
if _, err := w.Write(src); err != nil {
|
||||||
w.Close()
|
w.Close()
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -73,7 +79,7 @@ func Decompress(src []byte) ([]byte, error) {
|
|||||||
// Read uncompressed length (first 4 bytes)
|
// Read uncompressed length (first 4 bytes)
|
||||||
uncompressedLen := binary.BigEndian.Uint32(src[:4])
|
uncompressedLen := binary.BigEndian.Uint32(src[:4])
|
||||||
|
|
||||||
// Sanity check - prevent decompression bombs
|
// Sanity check
|
||||||
if uncompressedLen > MaxPacketSize {
|
if uncompressedLen > MaxPacketSize {
|
||||||
return nil, fmt.Errorf("uncompressed size %d exceeds max packet size", uncompressedLen)
|
return nil, fmt.Errorf("uncompressed size %d exceeds max packet size", uncompressedLen)
|
||||||
}
|
}
|
||||||
@ -95,16 +101,13 @@ func Decompress(src []byte) ([]byte, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ChatEncode encodes chat data using EQ's XOR-based encoding
|
// ChatEncode encodes chat data using EQ's XOR-based encoding
|
||||||
// EQ uses a simple rotating XOR with the encode key
|
|
||||||
func ChatEncode(buffer []byte, encodeKey int) {
|
func ChatEncode(buffer []byte, encodeKey int) {
|
||||||
if len(buffer) == 0 || encodeKey == 0 {
|
if len(buffer) == 0 || encodeKey == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// EQ chat encoding algorithm
|
|
||||||
key := byte(encodeKey & 0xFF)
|
key := byte(encodeKey & 0xFF)
|
||||||
for i := range buffer {
|
for i := range buffer {
|
||||||
// XOR with rotating key based on position
|
|
||||||
buffer[i] ^= key
|
buffer[i] ^= key
|
||||||
// Rotate key for next byte
|
// Rotate key for next byte
|
||||||
key = ((key << 1) | (key >> 7)) & 0xFF
|
key = ((key << 1) | (key >> 7)) & 0xFF
|
||||||
@ -115,23 +118,38 @@ func ChatEncode(buffer []byte, encodeKey int) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChatDecode decodes chat data using EQ's XOR-based encoding
|
// ChatDecode decodes chat data (XOR is symmetric)
|
||||||
// Decoding is the same as encoding for XOR
|
|
||||||
func ChatDecode(buffer []byte, decodeKey int) {
|
func ChatDecode(buffer []byte, decodeKey int) {
|
||||||
// XOR encoding is symmetric - encode and decode are the same operation
|
|
||||||
ChatEncode(buffer, decodeKey)
|
ChatEncode(buffer, decodeKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsChatPacket checks if opcode is a chat-related packet
|
||||||
|
func IsChatPacket(opcode uint16) bool {
|
||||||
|
chatOpcodes := map[uint16]bool{
|
||||||
|
0x0300: true, // OP_ChatMsg
|
||||||
|
0x0302: true, // OP_TellMsg
|
||||||
|
0x0307: true, // OP_ChatLeaveChannelMsg
|
||||||
|
0x0308: true, // OP_ChatTellChannelMsg
|
||||||
|
0x0309: true, // OP_ChatTellUserMsg
|
||||||
|
0x0e07: true, // OP_GuildsayMsg
|
||||||
|
}
|
||||||
|
return chatOpcodes[opcode]
|
||||||
|
}
|
||||||
|
|
||||||
|
// longToIP converts uint32 IP to string
|
||||||
|
func longToIP(ip uint32) string {
|
||||||
|
return fmt.Sprintf("%d.%d.%d.%d",
|
||||||
|
byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
|
||||||
|
}
|
||||||
|
|
||||||
// IsProtocolPacket checks if buffer contains a valid protocol packet
|
// IsProtocolPacket checks if buffer contains a valid protocol packet
|
||||||
func IsProtocolPacket(buffer []byte, trimCRC bool) bool {
|
func IsProtocolPacket(buffer []byte) bool {
|
||||||
if len(buffer) < 2 {
|
if len(buffer) < 2 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for valid protocol opcodes
|
|
||||||
opcode := binary.BigEndian.Uint16(buffer[:2])
|
opcode := binary.BigEndian.Uint16(buffer[:2])
|
||||||
|
|
||||||
// Protocol opcodes from protocol.go
|
|
||||||
validOpcodes := map[uint16]bool{
|
validOpcodes := map[uint16]bool{
|
||||||
0x0001: true, // OP_SessionRequest
|
0x0001: true, // OP_SessionRequest
|
||||||
0x0002: true, // OP_SessionResponse
|
0x0002: true, // OP_SessionResponse
|
||||||
@ -148,95 +166,5 @@ func IsProtocolPacket(buffer []byte, trimCRC bool) bool {
|
|||||||
0x001e: true, // OP_OutOfSession
|
0x001e: true, // OP_OutOfSession
|
||||||
}
|
}
|
||||||
|
|
||||||
if !validOpcodes[opcode] {
|
return validOpcodes[opcode]
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// If checking CRC, validate it
|
|
||||||
if trimCRC && len(buffer) >= 6 {
|
|
||||||
// Protocol packets have 2-byte opcode + data + 4-byte CRC
|
|
||||||
return ValidateCRC(buffer, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodePacket applies encoding/compression based on flags
|
|
||||||
func EncodePacket(packet *ProtoPacket, compressThreshold int, encodeKey int) error {
|
|
||||||
// Apply compression if packet is large enough
|
|
||||||
if len(packet.Buffer) > compressThreshold && !packet.IsCompressed() {
|
|
||||||
compressed, err := Compress(packet.Buffer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
packet.Buffer = compressed
|
|
||||||
packet.SetCompressed(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply chat encoding if this is a chat packet
|
|
||||||
if IsChatPacket(packet.Opcode) && encodeKey != 0 {
|
|
||||||
ChatEncode(packet.Buffer, encodeKey)
|
|
||||||
packet.SetEncrypted(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodePacket reverses encoding/compression
|
|
||||||
func DecodePacket(packet *ProtoPacket, decodeKey int) error {
|
|
||||||
// Decrypt if encrypted
|
|
||||||
if packet.IsEncrypted() && decodeKey != 0 {
|
|
||||||
ChatDecode(packet.Buffer, decodeKey)
|
|
||||||
packet.SetEncrypted(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decompress if compressed
|
|
||||||
if packet.IsCompressed() {
|
|
||||||
decompressed, err := Decompress(packet.Buffer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
packet.Buffer = decompressed
|
|
||||||
packet.SetCompressed(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsChatPacket checks if opcode is a chat-related packet
|
|
||||||
func IsChatPacket(opcode uint16) bool {
|
|
||||||
// Chat-related opcodes that need encoding
|
|
||||||
// These would map to OP_ChatMsg, OP_TellMsg, etc in the opcodes package
|
|
||||||
chatOpcodes := map[uint16]bool{
|
|
||||||
0x0300: true, // OP_ChatMsg
|
|
||||||
0x0302: true, // OP_TellMsg
|
|
||||||
0x0307: true, // OP_ChatLeaveChannelMsg
|
|
||||||
0x0308: true, // OP_ChatTellChannelMsg
|
|
||||||
0x0309: true, // OP_ChatTellUserMsg
|
|
||||||
0x0e07: true, // OP_GuildsayMsg
|
|
||||||
}
|
|
||||||
return chatOpcodes[opcode]
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to convert uint32 IP to string
|
|
||||||
func longToIP(ip uint32) string {
|
|
||||||
return fmt.Sprintf("%d.%d.%d.%d",
|
|
||||||
byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AppendCRC appends CRC16 to packet buffer using EQ2's custom CRC
|
|
||||||
func AppendCRC(buffer []byte, key uint32) []byte {
|
|
||||||
crc := crypto.CalculateCRC(buffer, key)
|
|
||||||
result := make([]byte, len(buffer)+2)
|
|
||||||
copy(result, buffer)
|
|
||||||
binary.BigEndian.PutUint16(result[len(buffer):], crc)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// StripCRC removes CRC16 from packet buffer
|
|
||||||
func StripCRC(buffer []byte) []byte {
|
|
||||||
if len(buffer) < 2 {
|
|
||||||
return buffer
|
|
||||||
}
|
|
||||||
return buffer[:len(buffer)-2]
|
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,15 @@ import (
|
|||||||
"git.sharkk.net/EQ2/Protocol/opcodes"
|
"git.sharkk.net/EQ2/Protocol/opcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProtoPacket handles low-level protocol features including EQ2-specific operations
|
// ProtoPacket handles low-level protocol features (matches EQProtocolPacket/EQ2Packet)
|
||||||
type ProtoPacket struct {
|
type ProtoPacket struct {
|
||||||
*Packet
|
*Packet
|
||||||
|
|
||||||
// Protocol state flags (using bitfield)
|
// Protocol state flags
|
||||||
flags uint8 // bit 0: compressed, bit 1: prepared, bit 2: encrypted, bit 3: acked
|
eq2Compressed bool
|
||||||
|
packetPrepared bool
|
||||||
|
packetEncrypted bool
|
||||||
|
acked bool
|
||||||
|
|
||||||
// EQ2-specific
|
// EQ2-specific
|
||||||
LoginOp opcodes.EmuOpcode
|
LoginOp opcodes.EmuOpcode
|
||||||
@ -30,15 +33,6 @@ type ProtoPacket struct {
|
|||||||
EncodeKey int
|
EncodeKey int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Protocol flag constants
|
|
||||||
const (
|
|
||||||
FlagCompressed = 1 << iota
|
|
||||||
FlagPrepared
|
|
||||||
FlagEncrypted
|
|
||||||
FlagAcked
|
|
||||||
)
|
|
||||||
|
|
||||||
// Default compression threshold
|
|
||||||
const DefaultCompressThreshold = 100
|
const DefaultCompressThreshold = 100
|
||||||
|
|
||||||
// NewProtoPacket creates a protocol packet with opcode and buffer
|
// NewProtoPacket creates a protocol packet with opcode and buffer
|
||||||
@ -50,7 +44,7 @@ func NewProtoPacket(op uint16, buf []byte, manager opcodes.Manager) *ProtoPacket
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProtoPacketFromRaw creates a protocol packet from raw buffer
|
// 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
|
||||||
var opcode uint16
|
var opcode uint16
|
||||||
@ -78,7 +72,6 @@ func NewProtoPacketFromRaw(buf []byte, opcodeOverride int, manager opcodes.Manag
|
|||||||
CompressThreshold: DefaultCompressThreshold,
|
CompressThreshold: DefaultCompressThreshold,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert EQ opcode to emulator opcode if manager available
|
|
||||||
if pp.manager != nil {
|
if pp.manager != nil {
|
||||||
pp.LoginOp = pp.manager.EQToEmu(opcode)
|
pp.LoginOp = pp.manager.EQToEmu(opcode)
|
||||||
}
|
}
|
||||||
@ -86,249 +79,63 @@ func NewProtoPacketFromRaw(buf []byte, opcodeOverride int, manager opcodes.Manag
|
|||||||
return pp
|
return pp
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetManager sets the opcode manager for translation
|
// PreparePacket prepares an EQ2 packet for transmission (matches EQ2Packet::PreparePacket)
|
||||||
func (p *ProtoPacket) SetManager(manager opcodes.Manager) {
|
|
||||||
p.manager = manager
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsCompressed returns true if packet is compressed
|
|
||||||
func (p *ProtoPacket) IsCompressed() bool {
|
|
||||||
return p.flags&FlagCompressed != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetCompressed sets the compressed flag
|
|
||||||
func (p *ProtoPacket) SetCompressed(compressed bool) {
|
|
||||||
if compressed {
|
|
||||||
p.flags |= FlagCompressed
|
|
||||||
} else {
|
|
||||||
p.flags &^= FlagCompressed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsPrepared returns true if packet has been prepared for sending
|
|
||||||
func (p *ProtoPacket) IsPrepared() bool {
|
|
||||||
return p.flags&FlagPrepared != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetPrepared sets the prepared flag
|
|
||||||
func (p *ProtoPacket) SetPrepared(prepared bool) {
|
|
||||||
if prepared {
|
|
||||||
p.flags |= FlagPrepared
|
|
||||||
} else {
|
|
||||||
p.flags &^= FlagPrepared
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsEncrypted returns true if packet is encrypted
|
|
||||||
func (p *ProtoPacket) IsEncrypted() bool {
|
|
||||||
return p.flags&FlagEncrypted != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetEncrypted sets the encrypted flag
|
|
||||||
func (p *ProtoPacket) SetEncrypted(encrypted bool) {
|
|
||||||
if encrypted {
|
|
||||||
p.flags |= FlagEncrypted
|
|
||||||
} else {
|
|
||||||
p.flags &^= FlagEncrypted
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsAcked returns true if packet has been acknowledged
|
|
||||||
func (p *ProtoPacket) IsAcked() bool {
|
|
||||||
return p.flags&FlagAcked != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAcked sets the acknowledged flag
|
|
||||||
func (p *ProtoPacket) SetAcked(acked bool) {
|
|
||||||
if acked {
|
|
||||||
p.flags |= FlagAcked
|
|
||||||
} else {
|
|
||||||
p.flags &^= FlagAcked
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CompressPacket compresses the packet data if needed
|
|
||||||
func (p *ProtoPacket) CompressPacket() error {
|
|
||||||
if p.IsCompressed() || len(p.Buffer) <= p.CompressThreshold {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
compressed, err := Compress(p.Buffer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only use compression if it actually saves space
|
|
||||||
if len(compressed) < len(p.Buffer) {
|
|
||||||
p.Buffer = compressed
|
|
||||||
p.SetCompressed(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecompressPacket decompresses the packet data
|
|
||||||
func (p *ProtoPacket) DecompressPacket() error {
|
|
||||||
if !p.IsCompressed() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
decompressed, err := Decompress(p.Buffer)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Buffer = decompressed
|
|
||||||
p.SetCompressed(false)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EncodeChat applies chat encoding if this is a chat packet
|
|
||||||
func (p *ProtoPacket) EncodeChat() {
|
|
||||||
if p.EncodeKey == 0 || p.IsEncrypted() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.IsChatPacket() {
|
|
||||||
ChatEncode(p.Buffer, p.EncodeKey)
|
|
||||||
p.SetEncrypted(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeChat reverses chat encoding
|
|
||||||
func (p *ProtoPacket) DecodeChat() {
|
|
||||||
if p.EncodeKey == 0 || !p.IsEncrypted() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ChatDecode(p.Buffer, p.EncodeKey)
|
|
||||||
p.SetEncrypted(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsChatPacket checks if this is a chat packet using opcode manager
|
|
||||||
func (p *ProtoPacket) IsChatPacket() bool {
|
|
||||||
if p.manager == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get emulator opcode
|
|
||||||
emuOp := p.manager.EQToEmu(p.Opcode)
|
|
||||||
|
|
||||||
// Check against known chat opcodes
|
|
||||||
switch emuOp {
|
|
||||||
case opcodes.OP_ChatMsg,
|
|
||||||
opcodes.OP_TellMsg,
|
|
||||||
opcodes.OP_ChatLeaveChannelMsg,
|
|
||||||
opcodes.OP_ChatTellChannelMsg,
|
|
||||||
opcodes.OP_ChatTellUserMsg,
|
|
||||||
opcodes.OP_GuildsayMsg:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy creates a deep copy of this protocol packet
|
|
||||||
func (p *ProtoPacket) Copy() *ProtoPacket {
|
|
||||||
newPacket := &ProtoPacket{
|
|
||||||
Packet: NewPacket(p.Opcode, p.Buffer),
|
|
||||||
flags: p.flags,
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize writes the protocol packet to a destination buffer
|
|
||||||
func (p *ProtoPacket) Serialize(dest []byte, offset int8) uint32 {
|
|
||||||
// Apply compression before serialization
|
|
||||||
p.CompressPacket()
|
|
||||||
|
|
||||||
// Apply chat encoding if needed
|
|
||||||
p.EncodeChat()
|
|
||||||
|
|
||||||
// Write compression flag if compressed
|
|
||||||
pos := 0
|
|
||||||
if p.IsCompressed() {
|
|
||||||
dest[pos] = 0x5a // EQ compression flag
|
|
||||||
pos++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write opcode (2 bytes)
|
|
||||||
if p.Opcode > 0xff {
|
|
||||||
binary.BigEndian.PutUint16(dest[pos:], p.Opcode)
|
|
||||||
} else {
|
|
||||||
dest[pos] = 0
|
|
||||||
dest[pos+1] = byte(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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PreparePacket prepares an EQ2 packet for transmission
|
|
||||||
func (p *ProtoPacket) PreparePacket(maxLen int16) int8 {
|
func (p *ProtoPacket) PreparePacket(maxLen int16) int8 {
|
||||||
if p.IsPrepared() {
|
if p.packetPrepared {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
p.SetPrepared(true)
|
if p.manager == nil {
|
||||||
|
|
||||||
// Apply compression if needed
|
|
||||||
if err := p.CompressPacket(); err != nil {
|
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply chat encoding if needed
|
p.packetPrepared = true
|
||||||
p.EncodeChat()
|
|
||||||
|
|
||||||
// Convert emulator opcode to network opcode using manager
|
// Convert emulator opcode to network opcode
|
||||||
var loginOpcode uint16
|
loginOpcode := p.manager.EmuToEQ(p.LoginOp)
|
||||||
if p.manager != nil {
|
|
||||||
loginOpcode = p.manager.EmuToEQ(p.LoginOp)
|
|
||||||
} else {
|
|
||||||
loginOpcode = uint16(p.LoginOp)
|
|
||||||
}
|
|
||||||
|
|
||||||
var offset int8
|
// Apply compression if needed
|
||||||
newSize := len(p.Buffer) + 2 + 1 // sequence(2) + compressed_flag(1)
|
if !p.eq2Compressed && len(p.Buffer) > p.CompressThreshold {
|
||||||
oversized := false
|
compressed, err := Compress(p.Buffer)
|
||||||
|
if err == nil && len(compressed) < len(p.Buffer) {
|
||||||
// Add compression flag space if compressed
|
p.Buffer = compressed
|
||||||
if p.IsCompressed() {
|
p.eq2Compressed = true
|
||||||
newSize++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle different opcode sizes and formats
|
|
||||||
if loginOpcode != 2 {
|
|
||||||
newSize++ // opcode type byte
|
|
||||||
if loginOpcode >= 255 {
|
|
||||||
newSize += 2 // oversized opcode
|
|
||||||
oversized = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build new packet buffer
|
// Build new packet buffer with proper headers
|
||||||
newBuffer := make([]byte, newSize)
|
var offset int8
|
||||||
ptr := 2 // Skip sequence field
|
newSize := len(p.Buffer) + 2 // Base size with sequence
|
||||||
|
|
||||||
// Add compression flag if needed
|
// Add compression flag space if compressed
|
||||||
if p.IsCompressed() {
|
if p.eq2Compressed {
|
||||||
newBuffer[ptr] = 0x5a // EQ compression flag
|
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++
|
ptr++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Encode opcode
|
||||||
if loginOpcode != 2 {
|
if loginOpcode != 2 {
|
||||||
if oversized {
|
if oversized {
|
||||||
newBuffer[ptr] = 0xff // Oversized marker
|
newBuffer[ptr] = 0xff // Oversized marker
|
||||||
@ -348,52 +155,94 @@ func (p *ProtoPacket) PreparePacket(maxLen int16) int8 {
|
|||||||
copy(newBuffer[ptr:], p.Buffer)
|
copy(newBuffer[ptr:], p.Buffer)
|
||||||
|
|
||||||
p.Buffer = newBuffer
|
p.Buffer = newBuffer
|
||||||
offset = int8(newSize - len(p.Buffer) - 1)
|
offset = int8(ptr - 2) // Return offset past sequence field
|
||||||
|
|
||||||
return offset
|
return offset
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeApplicationPacket converts protocol packet to application packet
|
// Serialize writes the protocol packet to a destination buffer
|
||||||
func (p *ProtoPacket) MakeApplicationPacket(opcodeSize uint8) *AppPacket {
|
func (p *ProtoPacket) Serialize(dest []byte, offset int8) uint32 {
|
||||||
// Decompress if needed
|
pos := 0
|
||||||
p.DecompressPacket()
|
|
||||||
|
|
||||||
// Decode chat if needed
|
// Write compression flag if compressed
|
||||||
p.DecodeChat()
|
if p.eq2Compressed {
|
||||||
|
dest[pos] = 0x5a
|
||||||
if opcodeSize == 0 {
|
pos++
|
||||||
opcodeSize = DefaultOpcodeSize
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app := &AppPacket{
|
// Write opcode (2 bytes)
|
||||||
Packet: NewPacket(0, p.Buffer),
|
binary.BigEndian.PutUint16(dest[pos:], p.Opcode)
|
||||||
opcodeSize: opcodeSize,
|
pos += 2
|
||||||
manager: p.manager,
|
|
||||||
}
|
|
||||||
app.CopyInfo(p.Packet)
|
|
||||||
|
|
||||||
// Parse opcode from buffer based on size
|
// Copy packet data after opcode
|
||||||
if opcodeSize == 1 && len(p.Buffer) >= 1 {
|
if offset < int8(len(p.Buffer)) {
|
||||||
app.Opcode = uint16(p.Buffer[0])
|
copy(dest[pos:], p.Buffer[offset:])
|
||||||
app.Buffer = p.Buffer[1:]
|
return uint32(len(p.Buffer)-int(offset)) + uint32(pos)
|
||||||
} else if len(p.Buffer) >= 2 {
|
|
||||||
app.Opcode = binary.BigEndian.Uint16(p.Buffer[:2])
|
|
||||||
app.Buffer = p.Buffer[2:]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert EQ opcode to emulator opcode if manager available
|
return uint32(pos)
|
||||||
if app.manager != nil {
|
|
||||||
app.emuOpcode = app.manager.EQToEmu(app.Opcode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return app
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppCombine combines this packet with another for efficient transmission
|
// 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 {
|
func (p *ProtoPacket) AppCombine(rhs *ProtoPacket) bool {
|
||||||
const opAppCombined = 0x19 // OP_AppCombined
|
const opAppCombined = 0x19 // OP_AppCombined
|
||||||
|
|
||||||
// Calculate sizes
|
|
||||||
lhsSize := p.Size()
|
lhsSize := p.Size()
|
||||||
rhsSize := rhs.Size()
|
rhsSize := rhs.Size()
|
||||||
|
|
||||||
@ -402,109 +251,136 @@ func (p *ProtoPacket) AppCombine(rhs *ProtoPacket) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this packet is already combined, add to it
|
// If already combined, add to it
|
||||||
if p.Opcode == opAppCombined {
|
if p.Opcode == opAppCombined {
|
||||||
// Calculate new size
|
tmpSize := rhsSize - 2 // Subtract opcode bytes
|
||||||
var newSize int
|
var newSize int
|
||||||
if rhsSize >= 255 {
|
|
||||||
newSize = len(p.Buffer) + int(rhsSize) + 3 // oversized header
|
if tmpSize >= 255 {
|
||||||
|
newSize = len(p.Buffer) + int(tmpSize) + 3 // oversized header
|
||||||
} else {
|
} else {
|
||||||
newSize = len(p.Buffer) + int(rhsSize) + 1 // normal header
|
newSize = len(p.Buffer) + int(tmpSize) + 1 // normal header
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check size limit
|
|
||||||
if newSize > MaxCombinedSize {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create new buffer
|
|
||||||
newBuffer := make([]byte, newSize)
|
newBuffer := make([]byte, newSize)
|
||||||
pos := 0
|
|
||||||
|
|
||||||
// Copy existing combined data
|
|
||||||
copy(newBuffer, p.Buffer)
|
copy(newBuffer, p.Buffer)
|
||||||
pos = len(p.Buffer)
|
pos := len(p.Buffer)
|
||||||
|
|
||||||
// Add new packet with size header
|
// Add size header
|
||||||
if rhsSize >= 255 {
|
if tmpSize >= 255 {
|
||||||
newBuffer[pos] = 255 // Oversized marker
|
newBuffer[pos] = 255 // Oversized marker
|
||||||
pos++
|
pos++
|
||||||
binary.BigEndian.PutUint16(newBuffer[pos:], uint16(rhsSize))
|
binary.BigEndian.PutUint16(newBuffer[pos:], uint16(tmpSize))
|
||||||
pos += 2
|
pos += 2
|
||||||
} else {
|
} else {
|
||||||
newBuffer[pos] = byte(rhsSize)
|
newBuffer[pos] = byte(tmpSize)
|
||||||
pos++
|
pos++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize rhs packet
|
// Serialize rhs packet (skip first 2 bytes - opcode)
|
||||||
tmpBuf := make([]byte, rhsSize)
|
if len(rhs.Buffer) > 2 {
|
||||||
rhs.Serialize(tmpBuf, 0)
|
copy(newBuffer[pos:], rhs.Buffer[2:])
|
||||||
copy(newBuffer[pos:], tmpBuf)
|
}
|
||||||
|
|
||||||
p.Buffer = newBuffer
|
p.Buffer = newBuffer
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new combined packet from two non-combined packets
|
// Create new combined packet
|
||||||
// Calculate total size: size1(1-3) + packet1 + size2(1-3) + packet2
|
lSize := lhsSize - 2
|
||||||
var totalSize int
|
rSize := rhsSize - 2
|
||||||
if lhsSize >= 255 {
|
totalSize := 0
|
||||||
totalSize += 3 + int(lhsSize)
|
|
||||||
|
if lSize >= 255 {
|
||||||
|
totalSize += 3 + int(lSize)
|
||||||
} else {
|
} else {
|
||||||
totalSize += 1 + int(lhsSize)
|
totalSize += 1 + int(lSize)
|
||||||
}
|
}
|
||||||
if rhsSize >= 255 {
|
|
||||||
totalSize += 3 + int(rhsSize)
|
if rSize >= 255 {
|
||||||
} else {
|
totalSize += 3 + int(rSize)
|
||||||
totalSize += 1 + int(rhsSize)
|
} else {
|
||||||
|
totalSize += 1 + int(rSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check size limit
|
|
||||||
if totalSize > MaxCombinedSize {
|
if totalSize > MaxCombinedSize {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build combined packet
|
|
||||||
newBuffer := make([]byte, totalSize)
|
newBuffer := make([]byte, totalSize)
|
||||||
pos := 0
|
pos := 0
|
||||||
|
|
||||||
// Add first packet with size header
|
// Add first packet
|
||||||
if lhsSize >= 255 {
|
if lSize >= 255 {
|
||||||
newBuffer[pos] = 255
|
newBuffer[pos] = 255
|
||||||
pos++
|
pos++
|
||||||
binary.BigEndian.PutUint16(newBuffer[pos:], uint16(lhsSize))
|
binary.BigEndian.PutUint16(newBuffer[pos:], uint16(lSize))
|
||||||
pos += 2
|
pos += 2
|
||||||
} else {
|
} else {
|
||||||
newBuffer[pos] = byte(lhsSize)
|
newBuffer[pos] = byte(lSize)
|
||||||
pos++
|
pos++
|
||||||
}
|
}
|
||||||
|
if len(p.Buffer) > 2 {
|
||||||
|
copy(newBuffer[pos:], p.Buffer[2:])
|
||||||
|
pos += int(lSize)
|
||||||
|
}
|
||||||
|
|
||||||
// Serialize first packet
|
// Add second packet
|
||||||
tmpBuf := make([]byte, lhsSize)
|
if rSize >= 255 {
|
||||||
p.Serialize(tmpBuf, 0)
|
|
||||||
copy(newBuffer[pos:], tmpBuf)
|
|
||||||
pos += int(lhsSize)
|
|
||||||
|
|
||||||
// Add second packet with size header
|
|
||||||
if rhsSize >= 255 {
|
|
||||||
newBuffer[pos] = 255
|
newBuffer[pos] = 255
|
||||||
pos++
|
pos++
|
||||||
binary.BigEndian.PutUint16(newBuffer[pos:], uint16(rhsSize))
|
binary.BigEndian.PutUint16(newBuffer[pos:], uint16(rSize))
|
||||||
pos += 2
|
pos += 2
|
||||||
} else {
|
} else {
|
||||||
newBuffer[pos] = byte(rhsSize)
|
newBuffer[pos] = byte(rSize)
|
||||||
pos++
|
pos++
|
||||||
}
|
}
|
||||||
|
if len(rhs.Buffer) > 2 {
|
||||||
|
copy(newBuffer[pos:], rhs.Buffer[2:])
|
||||||
|
}
|
||||||
|
|
||||||
// Serialize second packet
|
|
||||||
tmpBuf = make([]byte, rhsSize)
|
|
||||||
rhs.Serialize(tmpBuf, 0)
|
|
||||||
copy(newBuffer[pos:], tmpBuf)
|
|
||||||
|
|
||||||
// Update this packet to be combined
|
|
||||||
p.Opcode = opAppCombined
|
p.Opcode = opAppCombined
|
||||||
p.Buffer = newBuffer
|
p.Buffer = newBuffer
|
||||||
p.SetPrepared(false) // Need to re-prepare
|
p.packetPrepared = false
|
||||||
|
|
||||||
return true
|
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
|
||||||
|
}
|
||||||
|
323
stream/stream.go
323
stream/stream.go
@ -13,7 +13,7 @@ import (
|
|||||||
"github.com/panjf2000/gnet/v2"
|
"github.com/panjf2000/gnet/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stream implements EQ2's reliable UDP protocol
|
// Stream implements EQ2's reliable UDP protocol (matches EQStream)
|
||||||
type Stream struct {
|
type Stream struct {
|
||||||
conn gnet.Conn
|
conn gnet.Conn
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
@ -41,12 +41,15 @@ type Stream struct {
|
|||||||
duplicateAckCnt map[uint16]int
|
duplicateAckCnt map[uint16]int
|
||||||
|
|
||||||
// Fragment assembly with expiry
|
// Fragment assembly with expiry
|
||||||
fragments map[uint16]*fragmentBuffer
|
fragments map[uint16]*fragmentBuffer
|
||||||
nextFragID uint16
|
currentFragment *fragmentBuffer
|
||||||
|
|
||||||
// Out of order handling with expiry
|
// Out of order handling with expiry
|
||||||
outOfOrder map[uint16]*outOfOrderPacket
|
outOfOrder map[uint16]*outOfOrderPacket
|
||||||
|
|
||||||
|
// Combined packet for efficient sending
|
||||||
|
combinedPacket *packets.ProtoPacket
|
||||||
|
|
||||||
// Packet queues
|
// Packet queues
|
||||||
reliableQueue []*packets.ProtoPacket
|
reliableQueue []*packets.ProtoPacket
|
||||||
unreliableQueue []*packets.ProtoPacket
|
unreliableQueue []*packets.ProtoPacket
|
||||||
@ -64,6 +67,7 @@ type Stream struct {
|
|||||||
timeoutTimer *time.Timer
|
timeoutTimer *time.Timer
|
||||||
retransmitTimer *time.Timer
|
retransmitTimer *time.Timer
|
||||||
cleanupTimer *time.Timer
|
cleanupTimer *time.Timer
|
||||||
|
combineTimer *time.Timer
|
||||||
|
|
||||||
// Retransmission settings
|
// Retransmission settings
|
||||||
rtt time.Duration
|
rtt time.Duration
|
||||||
@ -121,6 +125,7 @@ const (
|
|||||||
fragmentTimeout = 30 * time.Second
|
fragmentTimeout = 30 * time.Second
|
||||||
outOfOrderTimeout = 10 * time.Second
|
outOfOrderTimeout = 10 * time.Second
|
||||||
duplicateAckThreshold = 3
|
duplicateAckThreshold = 3
|
||||||
|
combineFlushInterval = 10 * time.Millisecond
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config holds stream configuration
|
// Config holds stream configuration
|
||||||
@ -188,24 +193,23 @@ func NewStream(conn gnet.Conn, cfg *Config) *Stream {
|
|||||||
}
|
}
|
||||||
s.retransmitTimer = time.AfterFunc(retransmitInterval, s.processRetransmits)
|
s.retransmitTimer = time.AfterFunc(retransmitInterval, s.processRetransmits)
|
||||||
s.cleanupTimer = time.AfterFunc(cleanupInterval, s.cleanup)
|
s.cleanupTimer = time.AfterFunc(cleanupInterval, s.cleanup)
|
||||||
|
s.combineTimer = time.AfterFunc(combineFlushInterval, s.flushCombined)
|
||||||
|
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process handles incoming data from gnet
|
// Process handles incoming data with proper CRC validation
|
||||||
func (s *Stream) Process(data []byte) error {
|
func (s *Stream) Process(data []byte) error {
|
||||||
if len(data) < 2 {
|
if len(data) < 2 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for CRC
|
// Validate and strip CRC16
|
||||||
if len(data) > 2 {
|
if len(data) > 2 {
|
||||||
providedCRC := binary.BigEndian.Uint16(data[len(data)-2:])
|
if !packets.ValidateCRC(data, s.crcKey) {
|
||||||
calculatedCRC := crypto.CalculateCRC(data[:len(data)-2], s.crcKey)
|
|
||||||
if providedCRC != calculatedCRC {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
data = data[:len(data)-2]
|
data = packets.StripCRC(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decrypt if needed
|
// Decrypt if needed
|
||||||
@ -248,32 +252,71 @@ func (s *Stream) Process(data []byte) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendPacket sends an application packet
|
// SendPacket sends an application packet with proper preparation
|
||||||
func (s *Stream) SendPacket(app *packets.AppPacket) error {
|
func (s *Stream) SendPacket(app *packets.AppPacket) error {
|
||||||
if s.state.Load() != StateEstablished {
|
if s.state.Load() != StateEstablished {
|
||||||
return fmt.Errorf("stream not established")
|
return fmt.Errorf("stream not established")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate packet size
|
// Convert to protocol packet
|
||||||
if app.Size() > uint32(s.maxLen) {
|
proto := app.ToProto()
|
||||||
proto := s.appToProto(app)
|
proto.CompressThreshold = 100
|
||||||
|
proto.EncodeKey = s.encodeKey
|
||||||
|
|
||||||
|
// Check if packet needs fragmentation
|
||||||
|
if proto.Size() > uint32(s.maxLen-8) {
|
||||||
return s.sendFragmented(proto)
|
return s.sendFragmented(proto)
|
||||||
}
|
}
|
||||||
|
|
||||||
proto := s.appToProto(app)
|
// Try to combine with pending combined packet
|
||||||
|
s.mu.Lock()
|
||||||
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
|
if s.combinedPacket != nil {
|
||||||
|
if s.combinedPacket.AppCombine(proto) {
|
||||||
|
if s.combinedPacket.Size() > uint32(s.maxLen/2) {
|
||||||
|
s.flushCombinedPacketLocked()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
s.flushCombinedPacketLocked()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue based on reliability
|
||||||
isUnreliable := s.isUnreliableOpcode(app.GetOpcode())
|
isUnreliable := s.isUnreliableOpcode(app.GetOpcode())
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
if isUnreliable {
|
if isUnreliable {
|
||||||
s.unreliableQueue = append(s.unreliableQueue, proto)
|
s.unreliableQueue = append(s.unreliableQueue, proto)
|
||||||
} else {
|
} else {
|
||||||
s.reliableQueue = append(s.reliableQueue, proto)
|
// Start new combined packet if small enough
|
||||||
|
if proto.Size() < uint32(s.maxLen/4) {
|
||||||
|
s.combinedPacket = proto
|
||||||
|
s.combineTimer.Reset(combineFlushInterval)
|
||||||
|
} else {
|
||||||
|
s.reliableQueue = append(s.reliableQueue, proto)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
return s.processQueues()
|
return s.processQueues()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// flushCombined timer callback
|
||||||
|
func (s *Stream) flushCombined() {
|
||||||
|
s.mu.Lock()
|
||||||
|
s.flushCombinedPacketLocked()
|
||||||
|
s.mu.Unlock()
|
||||||
|
s.processQueues()
|
||||||
|
s.combineTimer.Reset(combineFlushInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
// flushCombinedPacketLocked sends any pending combined packet (must hold lock)
|
||||||
|
func (s *Stream) flushCombinedPacketLocked() {
|
||||||
|
if s.combinedPacket != nil {
|
||||||
|
s.reliableQueue = append(s.reliableQueue, s.combinedPacket)
|
||||||
|
s.combinedPacket = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// processQueues processes all packet queues
|
// processQueues processes all packet queues
|
||||||
func (s *Stream) processQueues() error {
|
func (s *Stream) processQueues() error {
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
@ -340,6 +383,75 @@ func (s *Stream) processQueues() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendFragmented sends a fragmented packet (matches EQStream::SendPacket)
|
||||||
|
func (s *Stream) sendFragmented(proto *packets.ProtoPacket) error {
|
||||||
|
// Prepare packet first
|
||||||
|
offset := proto.PreparePacket(int16(s.maxLen))
|
||||||
|
if offset < 0 {
|
||||||
|
return fmt.Errorf("failed to prepare packet")
|
||||||
|
}
|
||||||
|
|
||||||
|
data := proto.Buffer
|
||||||
|
length := uint32(len(data))
|
||||||
|
|
||||||
|
// First fragment with total length
|
||||||
|
out := packets.NewProtoPacket(opcodes.OP_Fragment, nil, s.opcodeManager)
|
||||||
|
out.Buffer = make([]byte, s.maxLen-4)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(out.Buffer[:4], length)
|
||||||
|
|
||||||
|
used := copy(out.Buffer[4:], data)
|
||||||
|
out.Buffer = out.Buffer[:4+used]
|
||||||
|
|
||||||
|
if err := s.sendReliable(out); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send remaining fragments
|
||||||
|
pos := used
|
||||||
|
for pos < int(length) {
|
||||||
|
chunkSize := int(length) - pos
|
||||||
|
if chunkSize > int(s.maxLen)-4 {
|
||||||
|
chunkSize = int(s.maxLen) - 4
|
||||||
|
}
|
||||||
|
|
||||||
|
frag := packets.NewProtoPacket(opcodes.OP_Fragment, data[pos:pos+chunkSize], s.opcodeManager)
|
||||||
|
if err := s.sendReliable(frag); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += chunkSize
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendReliable sends a packet reliably with sequencing
|
||||||
|
func (s *Stream) sendReliable(proto *packets.ProtoPacket) error {
|
||||||
|
s.mu.Lock()
|
||||||
|
seq := s.seqOut
|
||||||
|
s.seqOut = s.incrementSequence(s.seqOut)
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
data := make([]byte, proto.Size()+2)
|
||||||
|
binary.BigEndian.PutUint16(data[:2], seq)
|
||||||
|
proto.Serialize(data[2:], 0)
|
||||||
|
|
||||||
|
pending := &pendingPacket{
|
||||||
|
packet: proto.Copy(),
|
||||||
|
seq: seq,
|
||||||
|
sentTime: time.Now(),
|
||||||
|
attempts: 1,
|
||||||
|
nextRetry: time.Now().Add(s.rto),
|
||||||
|
}
|
||||||
|
|
||||||
|
s.mu.Lock()
|
||||||
|
s.pendingAcks[seq] = pending
|
||||||
|
s.mu.Unlock()
|
||||||
|
|
||||||
|
return s.sendRaw(opcodes.OP_Packet, data)
|
||||||
|
}
|
||||||
|
|
||||||
// Helper methods
|
// Helper methods
|
||||||
func (s *Stream) isUnreliableOpcode(emuOp opcodes.EmuOpcode) bool {
|
func (s *Stream) isUnreliableOpcode(emuOp opcodes.EmuOpcode) bool {
|
||||||
switch emuOp {
|
switch emuOp {
|
||||||
@ -363,6 +475,7 @@ func (s *Stream) isSequenceAhead(seq, base uint16) bool {
|
|||||||
return diff > 0 && diff < 0x8000
|
return diff > 0 && diff < 0x8000
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Protocol handlers
|
||||||
func (s *Stream) handleSessionRequest(data []byte) error {
|
func (s *Stream) handleSessionRequest(data []byte) error {
|
||||||
if len(data) < 10 {
|
if len(data) < 10 {
|
||||||
return fmt.Errorf("session request too small")
|
return fmt.Errorf("session request too small")
|
||||||
@ -430,6 +543,7 @@ func (s *Stream) handlePacket(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process any buffered out-of-order packets
|
||||||
for {
|
for {
|
||||||
if buffered, exists := s.outOfOrder[s.seqExpected]; exists {
|
if buffered, exists := s.outOfOrder[s.seqExpected]; exists {
|
||||||
delete(s.outOfOrder, s.seqExpected)
|
delete(s.outOfOrder, s.seqExpected)
|
||||||
@ -443,12 +557,14 @@ func (s *Stream) handlePacket(data []byte) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if s.isSequenceAhead(seq, s.seqExpected) {
|
} else if s.isSequenceAhead(seq, s.seqExpected) {
|
||||||
|
// Out of order - buffer it
|
||||||
s.outOfOrder[seq] = &outOfOrderPacket{
|
s.outOfOrder[seq] = &outOfOrderPacket{
|
||||||
data: append([]byte(nil), data...),
|
data: append([]byte(nil), data...),
|
||||||
timestamp: time.Now(),
|
timestamp: time.Now(),
|
||||||
}
|
}
|
||||||
go s.sendOutOfOrderAck(seq)
|
go s.sendOutOfOrderAck(seq)
|
||||||
} else {
|
} else {
|
||||||
|
// Duplicate packet - just ack it
|
||||||
go s.sendAckImmediate(seq)
|
go s.sendAckImmediate(seq)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -457,43 +573,46 @@ func (s *Stream) handlePacket(data []byte) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stream) handleFragment(data []byte) error {
|
func (s *Stream) handleFragment(data []byte) error {
|
||||||
if len(data) < 10 {
|
if len(data) < 2 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
seq := binary.BigEndian.Uint16(data[:2])
|
seq := binary.BigEndian.Uint16(data[:2])
|
||||||
fragID := binary.BigEndian.Uint32(data[2:6])
|
data = data[2:]
|
||||||
fragTotal := binary.BigEndian.Uint16(data[6:8])
|
|
||||||
fragCur := binary.BigEndian.Uint16(data[8:10])
|
|
||||||
data = data[10:]
|
|
||||||
|
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
defer s.mu.Unlock()
|
defer s.mu.Unlock()
|
||||||
|
|
||||||
frag, exists := s.fragments[uint16(fragID)]
|
// Check if this is start of new fragment stream
|
||||||
if !exists {
|
if s.currentFragment == nil && len(data) >= 4 {
|
||||||
frag = &fragmentBuffer{
|
totalLen := binary.BigEndian.Uint32(data[:4])
|
||||||
totalSize: uint32(fragTotal),
|
s.currentFragment = &fragmentBuffer{
|
||||||
|
totalSize: totalLen,
|
||||||
chunks: make(map[uint16][]byte),
|
chunks: make(map[uint16][]byte),
|
||||||
startTime: time.Now(),
|
startTime: time.Now(),
|
||||||
}
|
}
|
||||||
s.fragments[uint16(fragID)] = frag
|
|
||||||
}
|
|
||||||
|
|
||||||
frag.chunks[fragCur] = append([]byte(nil), data...)
|
if len(data) > 4 {
|
||||||
frag.received++
|
s.currentFragment.chunks[0] = append([]byte(nil), data[4:]...)
|
||||||
|
s.currentFragment.received = uint32(len(data) - 4)
|
||||||
if frag.received == uint32(fragTotal) {
|
}
|
||||||
complete := make([]byte, 0)
|
} else if s.currentFragment != nil {
|
||||||
for i := uint16(0); i < fragTotal; i++ {
|
// Continuation fragment
|
||||||
if chunk, ok := frag.chunks[i]; ok {
|
chunkNum := uint16(len(s.currentFragment.chunks))
|
||||||
complete = append(complete, chunk...)
|
s.currentFragment.chunks[chunkNum] = append([]byte(nil), data...)
|
||||||
} else {
|
s.currentFragment.received += uint32(len(data))
|
||||||
return nil
|
|
||||||
}
|
// Check if complete
|
||||||
|
if s.currentFragment.received >= s.currentFragment.totalSize {
|
||||||
|
complete := make([]byte, 0, s.currentFragment.totalSize)
|
||||||
|
for i := uint16(0); i < uint16(len(s.currentFragment.chunks)); i++ {
|
||||||
|
if chunk, ok := s.currentFragment.chunks[i]; ok {
|
||||||
|
complete = append(complete, chunk...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.currentFragment = nil
|
||||||
|
return s.processPacketData(complete)
|
||||||
}
|
}
|
||||||
delete(s.fragments, uint16(fragID))
|
|
||||||
return s.processPacketData(complete)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
s.ackQueue = append(s.ackQueue, seq)
|
s.ackQueue = append(s.ackQueue, seq)
|
||||||
@ -515,6 +634,7 @@ func (s *Stream) handleAck(data []byte) error {
|
|||||||
delete(s.pendingAcks, ackSeq)
|
delete(s.pendingAcks, ackSeq)
|
||||||
delete(s.duplicateAckCnt, ackSeq)
|
delete(s.duplicateAckCnt, ackSeq)
|
||||||
|
|
||||||
|
// Update RTT
|
||||||
sample := time.Since(pending.sentTime)
|
sample := time.Since(pending.sentTime)
|
||||||
if s.rtt == 0 {
|
if s.rtt == 0 {
|
||||||
s.rtt = sample
|
s.rtt = sample
|
||||||
@ -533,9 +653,11 @@ func (s *Stream) handleAck(data []byte) error {
|
|||||||
s.rto = s.maxRTO
|
s.rto = s.maxRTO
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// Duplicate ACK
|
||||||
s.duplicateAckCnt[ackSeq]++
|
s.duplicateAckCnt[ackSeq]++
|
||||||
|
|
||||||
if s.duplicateAckCnt[ackSeq] >= duplicateAckThreshold {
|
if s.duplicateAckCnt[ackSeq] >= duplicateAckThreshold {
|
||||||
|
// Fast retransmit
|
||||||
for seq, pending := range s.pendingAcks {
|
for seq, pending := range s.pendingAcks {
|
||||||
if s.isSequenceAhead(seq, ackSeq) {
|
if s.isSequenceAhead(seq, ackSeq) {
|
||||||
s.resendQueue = append(s.resendQueue, pending)
|
s.resendQueue = append(s.resendQueue, pending)
|
||||||
@ -641,83 +763,19 @@ func (s *Stream) handleDisconnect() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stream) sendFragmented(proto *packets.ProtoPacket) error {
|
// processPacketData processes received packet data
|
||||||
data := make([]byte, proto.Size())
|
|
||||||
proto.Serialize(data, 0)
|
|
||||||
|
|
||||||
fragSize := int(s.maxLen) - 12
|
|
||||||
numFrags := (len(data) + fragSize - 1) / fragSize
|
|
||||||
|
|
||||||
if numFrags > 0xFFFF {
|
|
||||||
return fmt.Errorf("packet too large to fragment")
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
fragID := s.nextFragID
|
|
||||||
s.nextFragID++
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
for i := 0; i < numFrags; i++ {
|
|
||||||
start := i * fragSize
|
|
||||||
end := start + fragSize
|
|
||||||
if end > len(data) {
|
|
||||||
end = len(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fragHeader := make([]byte, 10)
|
|
||||||
binary.BigEndian.PutUint32(fragHeader[0:4], uint32(fragID))
|
|
||||||
binary.BigEndian.PutUint16(fragHeader[4:6], uint16(numFrags))
|
|
||||||
binary.BigEndian.PutUint16(fragHeader[6:8], uint16(i))
|
|
||||||
binary.BigEndian.PutUint16(fragHeader[8:10], uint16(end-start))
|
|
||||||
|
|
||||||
fragment := append(fragHeader, data[start:end]...)
|
|
||||||
fragProto := packets.NewProtoPacket(opcodes.OP_Fragment, fragment, s.opcodeManager)
|
|
||||||
|
|
||||||
if err := s.sendReliable(fragProto); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) sendReliable(proto *packets.ProtoPacket) error {
|
|
||||||
s.mu.Lock()
|
|
||||||
seq := s.seqOut
|
|
||||||
s.seqOut = s.incrementSequence(s.seqOut)
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
data := make([]byte, proto.Size()+2)
|
|
||||||
binary.BigEndian.PutUint16(data[:2], seq)
|
|
||||||
proto.Serialize(data[2:], 0)
|
|
||||||
|
|
||||||
pending := &pendingPacket{
|
|
||||||
packet: proto.Copy(),
|
|
||||||
seq: seq,
|
|
||||||
sentTime: time.Now(),
|
|
||||||
attempts: 1,
|
|
||||||
nextRetry: time.Now().Add(s.rto),
|
|
||||||
}
|
|
||||||
|
|
||||||
s.mu.Lock()
|
|
||||||
s.pendingAcks[seq] = pending
|
|
||||||
s.mu.Unlock()
|
|
||||||
|
|
||||||
return s.sendRaw(opcodes.OP_Packet, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Stream) processPacketData(data []byte) error {
|
func (s *Stream) processPacketData(data []byte) error {
|
||||||
proto := packets.NewProtoPacketFromRaw(data, -1, s.opcodeManager)
|
proto := packets.NewProtoPacketFromRaw(data, -1, s.opcodeManager)
|
||||||
if err := proto.DecompressPacket(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
app := proto.MakeApplicationPacket(s.opcodeSize)
|
app := proto.MakeApplicationPacket(s.opcodeSize)
|
||||||
|
|
||||||
if s.onPacket != nil {
|
if s.onPacket != nil {
|
||||||
go s.onPacket(app)
|
go s.onPacket(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Timer handlers
|
||||||
func (s *Stream) processRetransmits() {
|
func (s *Stream) processRetransmits() {
|
||||||
if s.state.Load() != StateEstablished {
|
if s.state.Load() != StateEstablished {
|
||||||
return
|
return
|
||||||
@ -757,18 +815,21 @@ func (s *Stream) cleanup() {
|
|||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
|
// Clean up old fragments
|
||||||
for id, frag := range s.fragments {
|
for id, frag := range s.fragments {
|
||||||
if now.Sub(frag.startTime) > fragmentTimeout {
|
if now.Sub(frag.startTime) > fragmentTimeout {
|
||||||
delete(s.fragments, id)
|
delete(s.fragments, id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up old out-of-order packets
|
||||||
for seq, oop := range s.outOfOrder {
|
for seq, oop := range s.outOfOrder {
|
||||||
if now.Sub(oop.timestamp) > outOfOrderTimeout {
|
if now.Sub(oop.timestamp) > outOfOrderTimeout {
|
||||||
delete(s.outOfOrder, seq)
|
delete(s.outOfOrder, seq)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up duplicate ACK counts
|
||||||
for seq := range s.duplicateAckCnt {
|
for seq := range s.duplicateAckCnt {
|
||||||
if _, exists := s.pendingAcks[seq]; !exists {
|
if _, exists := s.pendingAcks[seq]; !exists {
|
||||||
delete(s.duplicateAckCnt, seq)
|
delete(s.duplicateAckCnt, seq)
|
||||||
@ -841,14 +902,16 @@ func (s *Stream) handleTimeout() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sendRaw sends raw packet with CRC16
|
||||||
func (s *Stream) sendRaw(opcode uint16, data []byte) error {
|
func (s *Stream) sendRaw(opcode uint16, data []byte) error {
|
||||||
packet := make([]byte, 2+len(data)+2)
|
packet := make([]byte, 2+len(data))
|
||||||
binary.BigEndian.PutUint16(packet[:2], opcode)
|
binary.BigEndian.PutUint16(packet[:2], opcode)
|
||||||
copy(packet[2:], data)
|
copy(packet[2:], data)
|
||||||
|
|
||||||
crc := crypto.CalculateCRC(packet[:len(packet)-2], s.crcKey)
|
// Add CRC16
|
||||||
binary.BigEndian.PutUint16(packet[len(packet)-2:], crc)
|
packet = packets.AppendCRC(packet, s.crcKey)
|
||||||
|
|
||||||
|
// Encrypt if needed
|
||||||
if s.cipher != nil {
|
if s.cipher != nil {
|
||||||
s.cipher.Encrypt(packet)
|
s.cipher.Encrypt(packet)
|
||||||
}
|
}
|
||||||
@ -859,14 +922,6 @@ func (s *Stream) sendRaw(opcode uint16, data []byte) error {
|
|||||||
return s.conn.AsyncWrite(packet, nil)
|
return s.conn.AsyncWrite(packet, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Stream) appToProto(app *packets.AppPacket) *packets.ProtoPacket {
|
|
||||||
proto := packets.NewProtoPacket(app.Opcode, app.Buffer, s.opcodeManager)
|
|
||||||
proto.CopyInfo(app.Packet)
|
|
||||||
proto.CompressThreshold = 100
|
|
||||||
proto.EncodeKey = s.encodeKey
|
|
||||||
return proto
|
|
||||||
}
|
|
||||||
|
|
||||||
// Public methods
|
// Public methods
|
||||||
func (s *Stream) SetPacketCallback(fn func(*packets.AppPacket)) {
|
func (s *Stream) SetPacketCallback(fn func(*packets.AppPacket)) {
|
||||||
s.onPacket = fn
|
s.onPacket = fn
|
||||||
@ -886,7 +941,7 @@ func (s *Stream) IsConnected() bool {
|
|||||||
|
|
||||||
func (s *Stream) SendSessionRequest() error {
|
func (s *Stream) SendSessionRequest() error {
|
||||||
data := make([]byte, 10)
|
data := make([]byte, 10)
|
||||||
binary.BigEndian.PutUint32(data[0:4], 2)
|
binary.BigEndian.PutUint32(data[0:4], 2) // protocol version
|
||||||
binary.BigEndian.PutUint32(data[4:8], s.sessionID)
|
binary.BigEndian.PutUint32(data[4:8], s.sessionID)
|
||||||
binary.BigEndian.PutUint16(data[8:10], s.maxLen)
|
binary.BigEndian.PutUint16(data[8:10], s.maxLen)
|
||||||
|
|
||||||
@ -921,6 +976,7 @@ func (s *Stream) Close() error {
|
|||||||
s.state.Store(StateClosed)
|
s.state.Store(StateClosed)
|
||||||
s.sendRaw(opcodes.OP_SessionDisconnect, nil)
|
s.sendRaw(opcodes.OP_SessionDisconnect, nil)
|
||||||
|
|
||||||
|
// Stop all timers
|
||||||
if s.keepAliveTimer != nil {
|
if s.keepAliveTimer != nil {
|
||||||
s.keepAliveTimer.Stop()
|
s.keepAliveTimer.Stop()
|
||||||
}
|
}
|
||||||
@ -936,7 +992,11 @@ func (s *Stream) Close() error {
|
|||||||
if s.cleanupTimer != nil {
|
if s.cleanupTimer != nil {
|
||||||
s.cleanupTimer.Stop()
|
s.cleanupTimer.Stop()
|
||||||
}
|
}
|
||||||
|
if s.combineTimer != nil {
|
||||||
|
s.combineTimer.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear queues
|
||||||
s.mu.Lock()
|
s.mu.Lock()
|
||||||
s.reliableQueue = nil
|
s.reliableQueue = nil
|
||||||
s.unreliableQueue = nil
|
s.unreliableQueue = nil
|
||||||
@ -944,11 +1004,32 @@ func (s *Stream) Close() error {
|
|||||||
s.pendingAcks = nil
|
s.pendingAcks = nil
|
||||||
s.fragments = nil
|
s.fragments = nil
|
||||||
s.outOfOrder = nil
|
s.outOfOrder = nil
|
||||||
|
s.combinedPacket = nil
|
||||||
|
s.currentFragment = nil
|
||||||
s.mu.Unlock()
|
s.mu.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetStats returns stream statistics
|
||||||
|
func (s *Stream) GetStats() map[string]interface{} {
|
||||||
|
s.mu.RLock()
|
||||||
|
defer s.mu.RUnlock()
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"packets_out": atomic.LoadUint64(&s.packetsOut),
|
||||||
|
"packets_in": atomic.LoadUint64(&s.packetsIn),
|
||||||
|
"bytes_out": atomic.LoadUint64(&s.bytesOut),
|
||||||
|
"bytes_in": atomic.LoadUint64(&s.bytesIn),
|
||||||
|
"retransmits": atomic.LoadUint64(&s.retransmits),
|
||||||
|
"pending_acks": len(s.pendingAcks),
|
||||||
|
"out_of_order": len(s.outOfOrder),
|
||||||
|
"fragments": len(s.fragments),
|
||||||
|
"rtt": s.rtt,
|
||||||
|
"rto": s.rto,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func absTime(d time.Duration) time.Duration {
|
func absTime(d time.Duration) time.Duration {
|
||||||
if d < 0 {
|
if d < 0 {
|
||||||
return -d
|
return -d
|
||||||
|
Loading…
x
Reference in New Issue
Block a user