675 lines
16 KiB
Go
675 lines
16 KiB
Go
package eq2net
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"net"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// StreamState represents the connection state
|
|
type StreamState int
|
|
|
|
const (
|
|
Established StreamState = iota
|
|
WaitClose
|
|
Closing
|
|
Disconnecting
|
|
Closed
|
|
)
|
|
|
|
// StreamType represents different stream types
|
|
type StreamType int
|
|
|
|
const (
|
|
UnknownStream StreamType = iota
|
|
LoginStream
|
|
WorldStream
|
|
ZoneStream
|
|
ChatOrMailStream
|
|
ChatStream
|
|
MailStream
|
|
EQ2Stream
|
|
)
|
|
|
|
// SessionRequest packet structure
|
|
type SessionRequest struct {
|
|
UnknownA uint32
|
|
Session uint32
|
|
MaxLength uint32
|
|
}
|
|
|
|
// SessionResponse packet structure
|
|
type SessionResponse struct {
|
|
Session uint32
|
|
Key uint32
|
|
UnknownA uint8
|
|
Format uint8
|
|
UnknownB uint8
|
|
MaxLength uint32
|
|
UnknownD uint32
|
|
}
|
|
|
|
// ClientSessionStats for tracking connection statistics
|
|
type ClientSessionStats struct {
|
|
RequestID uint16
|
|
LastLocalDelta uint32
|
|
AverageDelta uint32
|
|
LowDelta uint32
|
|
HighDelta uint32
|
|
LastRemoteDelta uint32
|
|
PacketsSent uint64
|
|
PacketsReceived uint64
|
|
}
|
|
|
|
// ServerSessionStats for server-side statistics
|
|
type ServerSessionStats struct {
|
|
RequestID uint16
|
|
CurrentTime uint32
|
|
Unknown1 uint32
|
|
ReceivedPackets uint32
|
|
Unknown2 uint32
|
|
SentPackets uint32
|
|
Unknown3 uint32
|
|
SentPackets2 uint32
|
|
Unknown4 uint32
|
|
ReceivedPackets2 uint32
|
|
}
|
|
|
|
// Stream represents a connection stream
|
|
type Stream struct {
|
|
mu sync.RWMutex
|
|
|
|
// Connection info
|
|
remoteAddr *net.UDPAddr
|
|
remoteIP net.IP
|
|
remotePort uint16
|
|
localAddr *net.UDPAddr
|
|
sessionID uint32
|
|
key uint32
|
|
state StreamState
|
|
streamType StreamType
|
|
|
|
// Buffers
|
|
buffer [8192]byte
|
|
oversizeBuffer []byte
|
|
oversizeOffset uint32
|
|
oversizeLength uint32
|
|
rogueBuffer []byte
|
|
rogueBufOffset uint32
|
|
rogueBufSize uint32
|
|
|
|
// Packet tracking
|
|
receivedPackets uint32
|
|
sentPackets uint32
|
|
nextInSeq uint16
|
|
nextOutSeq uint16
|
|
lastAckSent uint16
|
|
lastAckReceived uint16
|
|
|
|
// Timing
|
|
lastReceiveTime time.Time
|
|
lastSendTime time.Time
|
|
avgRoundTrip time.Duration
|
|
minRoundTrip time.Duration
|
|
maxRoundTrip time.Duration
|
|
retransmitTimeout time.Duration
|
|
|
|
// Queues
|
|
incomingQueue []*ProtocolPacket
|
|
outgoingQueue []*ProtocolPacket
|
|
sentQueue map[uint16]*ProtocolPacket
|
|
futurePackets map[uint16]*ProtocolPacket
|
|
|
|
// Options
|
|
compressed bool
|
|
encoded bool
|
|
maxLength uint32
|
|
opcodeSize uint8
|
|
|
|
// Callbacks
|
|
onPacket func(*ApplicationPacket)
|
|
onDisconnect func()
|
|
}
|
|
|
|
// NewStream creates a new stream
|
|
func NewStream(remoteAddr *net.UDPAddr, streamType StreamType) *Stream {
|
|
s := &Stream{
|
|
remoteAddr: remoteAddr,
|
|
remoteIP: remoteAddr.IP,
|
|
remotePort: uint16(remoteAddr.Port),
|
|
streamType: streamType,
|
|
state: Established,
|
|
sentQueue: make(map[uint16]*ProtocolPacket),
|
|
futurePackets: make(map[uint16]*ProtocolPacket),
|
|
lastReceiveTime: time.Now(),
|
|
lastSendTime: time.Now(),
|
|
retransmitTimeout: 500 * time.Millisecond,
|
|
maxLength: 512,
|
|
opcodeSize: 2,
|
|
}
|
|
|
|
// Set opcode size based on stream type
|
|
if streamType == LoginStream || streamType == ChatStream || streamType == MailStream {
|
|
s.opcodeSize = 1
|
|
}
|
|
|
|
return s
|
|
}
|
|
|
|
// SetSessionInfo sets the session ID and key
|
|
func (s *Stream) SetSessionInfo(sessionID uint32, key uint32) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.sessionID = sessionID
|
|
s.key = key
|
|
}
|
|
|
|
// GetSessionID returns the session ID
|
|
func (s *Stream) GetSessionID() uint32 {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.sessionID
|
|
}
|
|
|
|
// GetKey returns the session key
|
|
func (s *Stream) GetKey() uint32 {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.key
|
|
}
|
|
|
|
// GetState returns the current stream state
|
|
func (s *Stream) GetState() StreamState {
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
return s.state
|
|
}
|
|
|
|
// SetState sets the stream state
|
|
func (s *Stream) SetState(state StreamState) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.state = state
|
|
}
|
|
|
|
// GetRemoteAddr returns the remote address
|
|
func (s *Stream) GetRemoteAddr() *net.UDPAddr {
|
|
return s.remoteAddr
|
|
}
|
|
|
|
// Process processes an incoming packet
|
|
func (s *Stream) Process(data []byte) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
s.lastReceiveTime = time.Now()
|
|
s.receivedPackets++
|
|
|
|
// Parse the packet
|
|
if len(data) < 2 {
|
|
return fmt.Errorf("packet too small")
|
|
}
|
|
|
|
opcode := binary.BigEndian.Uint16(data)
|
|
|
|
// Handle different packet types
|
|
switch uint8(opcode & 0xFF) {
|
|
case OPSessionRequest:
|
|
return s.handleSessionRequest(data)
|
|
case OPSessionResponse:
|
|
return s.handleSessionResponse(data)
|
|
case OPKeepAlive:
|
|
return s.handleKeepAlive(data)
|
|
case OPPacket:
|
|
return s.handlePacket(data)
|
|
case OPFragment:
|
|
return s.handleFragment(data)
|
|
case OPAck:
|
|
return s.handleAck(data)
|
|
case OPOutOfOrderAck:
|
|
return s.handleOutOfOrderAck(data)
|
|
case OPSessionDisconnect:
|
|
return s.handleDisconnect(data)
|
|
case OPCombined:
|
|
return s.handleCombined(data)
|
|
case OPAppCombined:
|
|
return s.handleAppCombined(data)
|
|
default:
|
|
return fmt.Errorf("unknown opcode: %02x", opcode&0xFF)
|
|
}
|
|
}
|
|
|
|
// handleSessionRequest handles session request packets
|
|
func (s *Stream) handleSessionRequest(data []byte) error {
|
|
if len(data) < 14 { // 2 byte opcode + 12 byte SessionRequest
|
|
return fmt.Errorf("session request too small")
|
|
}
|
|
|
|
req := &SessionRequest{}
|
|
req.UnknownA = binary.LittleEndian.Uint32(data[2:6])
|
|
req.Session = binary.LittleEndian.Uint32(data[6:10])
|
|
req.MaxLength = binary.LittleEndian.Uint32(data[10:14])
|
|
|
|
s.sessionID = req.Session
|
|
s.maxLength = req.MaxLength
|
|
|
|
// Send session response
|
|
return s.sendSessionResponse()
|
|
}
|
|
|
|
// handleSessionResponse handles session response packets
|
|
func (s *Stream) handleSessionResponse(data []byte) error {
|
|
if len(data) < 21 { // 2 byte opcode + 19 byte SessionResponse
|
|
return fmt.Errorf("session response too small")
|
|
}
|
|
|
|
resp := &SessionResponse{}
|
|
resp.Session = binary.LittleEndian.Uint32(data[2:6])
|
|
resp.Key = binary.LittleEndian.Uint32(data[6:10])
|
|
resp.UnknownA = data[10]
|
|
resp.Format = data[11]
|
|
resp.UnknownB = data[12]
|
|
resp.MaxLength = binary.LittleEndian.Uint32(data[13:17])
|
|
resp.UnknownD = binary.LittleEndian.Uint32(data[17:21])
|
|
|
|
s.sessionID = resp.Session
|
|
s.key = resp.Key
|
|
s.maxLength = resp.MaxLength
|
|
s.compressed = (resp.Format & 0x01) != 0
|
|
s.encoded = (resp.Format & 0x04) != 0
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleKeepAlive handles keep-alive packets
|
|
func (s *Stream) handleKeepAlive(data []byte) error {
|
|
// Send keep-alive response
|
|
return s.sendKeepAliveResponse()
|
|
}
|
|
|
|
// handlePacket handles regular data packets
|
|
func (s *Stream) handlePacket(data []byte) error {
|
|
if len(data) < 4 {
|
|
return fmt.Errorf("packet too small")
|
|
}
|
|
|
|
// Extract sequence number
|
|
sequence := binary.BigEndian.Uint16(data[2:4])
|
|
|
|
// Validate CRC
|
|
if !ValidateCRC(data, len(data), s.key) {
|
|
return fmt.Errorf("CRC validation failed")
|
|
}
|
|
|
|
// Create protocol packet
|
|
packet := NewProtocolPacket(data[4:len(data)-2], -1) // Skip header and CRC
|
|
packet.Sequence = sequence
|
|
|
|
// Handle sequence
|
|
if sequence == s.nextInSeq {
|
|
// In order packet
|
|
s.processInOrderPacket(packet)
|
|
s.nextInSeq++
|
|
|
|
// Check for any future packets that are now in order
|
|
s.processFuturePackets()
|
|
|
|
// Send ACK
|
|
s.sendAck(sequence)
|
|
} else if sequence > s.nextInSeq {
|
|
// Future packet - store it
|
|
s.futurePackets[sequence] = packet
|
|
// Send out of order ACK
|
|
s.sendOutOfOrderAck(sequence)
|
|
}
|
|
// Ignore past packets (already processed)
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleFragment handles fragmented packets
|
|
func (s *Stream) handleFragment(data []byte) error {
|
|
if len(data) < 8 {
|
|
return fmt.Errorf("fragment too small")
|
|
}
|
|
|
|
sequence := binary.BigEndian.Uint16(data[2:4])
|
|
totalSize := binary.BigEndian.Uint32(data[4:8])
|
|
|
|
// Validate CRC
|
|
if !ValidateCRC(data, len(data), s.key) {
|
|
return fmt.Errorf("CRC validation failed")
|
|
}
|
|
|
|
// Initialize oversize buffer if needed
|
|
if s.oversizeBuffer == nil || s.oversizeLength != totalSize {
|
|
s.oversizeBuffer = make([]byte, totalSize)
|
|
s.oversizeOffset = 0
|
|
s.oversizeLength = totalSize
|
|
}
|
|
|
|
// Copy fragment data
|
|
fragmentData := data[8 : len(data)-2] // Skip header and CRC
|
|
copy(s.oversizeBuffer[s.oversizeOffset:], fragmentData)
|
|
s.oversizeOffset += uint32(len(fragmentData))
|
|
|
|
// Send ACK
|
|
s.sendAck(sequence)
|
|
s.nextInSeq = sequence + 1
|
|
|
|
// Check if we have the complete packet
|
|
if s.oversizeOffset >= s.oversizeLength {
|
|
// Process the complete packet
|
|
packet := NewProtocolPacket(s.oversizeBuffer[:s.oversizeLength], -1)
|
|
s.processInOrderPacket(packet)
|
|
|
|
// Reset oversize buffer
|
|
s.oversizeBuffer = nil
|
|
s.oversizeOffset = 0
|
|
s.oversizeLength = 0
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleAck handles acknowledgment packets
|
|
func (s *Stream) handleAck(data []byte) error {
|
|
if len(data) < 4 {
|
|
return fmt.Errorf("ack too small")
|
|
}
|
|
|
|
sequence := binary.BigEndian.Uint16(data[2:4])
|
|
|
|
// Remove acknowledged packet from sent queue
|
|
if packet, ok := s.sentQueue[sequence]; ok {
|
|
packet.Acked = true
|
|
delete(s.sentQueue, sequence)
|
|
|
|
// Update RTT based on this ACK
|
|
if packet.SentTime > 0 {
|
|
rtt := time.Since(time.Unix(int64(packet.SentTime), 0))
|
|
s.updateRTT(rtt)
|
|
}
|
|
}
|
|
|
|
s.lastAckReceived = sequence
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleOutOfOrderAck handles out-of-order acknowledgments
|
|
func (s *Stream) handleOutOfOrderAck(data []byte) error {
|
|
if len(data) < 4 {
|
|
return fmt.Errorf("out of order ack too small")
|
|
}
|
|
|
|
sequence := binary.BigEndian.Uint16(data[2:4])
|
|
|
|
// Resend packets between lastAckReceived and sequence
|
|
for seq := s.lastAckReceived + 1; seq < sequence; seq++ {
|
|
if packet, ok := s.sentQueue[seq]; ok {
|
|
s.resendPacket(packet)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleDisconnect handles disconnect packets
|
|
func (s *Stream) handleDisconnect(data []byte) error {
|
|
s.state = Disconnecting
|
|
|
|
// Call disconnect callback
|
|
if s.onDisconnect != nil {
|
|
s.onDisconnect()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleCombined handles combined packets
|
|
func (s *Stream) handleCombined(data []byte) error {
|
|
if len(data) < 3 {
|
|
return fmt.Errorf("combined packet too small")
|
|
}
|
|
|
|
offset := 2 // Skip opcode
|
|
|
|
for offset < len(data)-2 { // Leave room for CRC
|
|
if offset+1 >= len(data) {
|
|
break
|
|
}
|
|
|
|
// Read sub-packet length
|
|
length := uint16(data[offset])
|
|
offset++
|
|
|
|
if length == 0xFF {
|
|
// Extended length
|
|
if offset+2 >= len(data) {
|
|
break
|
|
}
|
|
length = binary.BigEndian.Uint16(data[offset:])
|
|
offset += 2
|
|
}
|
|
|
|
// Extract sub-packet
|
|
if offset+int(length) > len(data) {
|
|
break
|
|
}
|
|
|
|
subPacket := data[offset : offset+int(length)]
|
|
offset += int(length)
|
|
|
|
// Process sub-packet
|
|
s.Process(subPacket)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// handleAppCombined handles application-level combined packets
|
|
func (s *Stream) handleAppCombined(data []byte) error {
|
|
// Similar to handleCombined but for application packets
|
|
return s.handleCombined(data)
|
|
}
|
|
|
|
// processInOrderPacket processes an in-order packet
|
|
func (s *Stream) processInOrderPacket(packet *ProtocolPacket) {
|
|
// Decompress if needed
|
|
if s.compressed && packet.EQ2Compressed {
|
|
decompressed := make([]byte, 8192)
|
|
size, err := Decompress(packet.Buffer, uint32(len(packet.Buffer)), decompressed, 8192)
|
|
if err == nil {
|
|
packet.Buffer = decompressed[:size]
|
|
packet.Size = size
|
|
}
|
|
}
|
|
|
|
// Decode if needed
|
|
if s.encoded && packet.PacketEncrypted {
|
|
ChatDecode(packet.Buffer, len(packet.Buffer), int(s.key))
|
|
packet.PacketEncrypted = false
|
|
}
|
|
|
|
// Convert to application packet and call callback
|
|
if s.onPacket != nil {
|
|
appPacket := s.makeApplicationPacket(packet)
|
|
if appPacket != nil {
|
|
s.onPacket(appPacket)
|
|
}
|
|
}
|
|
}
|
|
|
|
// processFuturePackets processes any future packets that are now in order
|
|
func (s *Stream) processFuturePackets() {
|
|
for {
|
|
if packet, ok := s.futurePackets[s.nextInSeq]; ok {
|
|
s.processInOrderPacket(packet)
|
|
delete(s.futurePackets, s.nextInSeq)
|
|
s.nextInSeq++
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// makeApplicationPacket converts a protocol packet to an application packet
|
|
func (s *Stream) makeApplicationPacket(packet *ProtocolPacket) *ApplicationPacket {
|
|
if len(packet.Buffer) < int(s.opcodeSize) {
|
|
return nil
|
|
}
|
|
|
|
var opcode uint16
|
|
var data []byte
|
|
|
|
if s.opcodeSize == 1 {
|
|
opcode = uint16(packet.Buffer[0])
|
|
data = packet.Buffer[1:]
|
|
} else {
|
|
opcode = binary.LittleEndian.Uint16(packet.Buffer)
|
|
data = packet.Buffer[2:]
|
|
}
|
|
|
|
appPacket := NewApplicationPacket(EmuOpcode(opcode), data)
|
|
appPacket.AppOpcodeSize = s.opcodeSize
|
|
|
|
return appPacket
|
|
}
|
|
|
|
// Send queues a packet for sending
|
|
func (s *Stream) Send(packet *ApplicationPacket) error {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
|
|
// Convert to protocol packet
|
|
data := packet.Serialize()
|
|
protocolPacket := NewProtocolPacketWithOp(uint16(OPPacket), data)
|
|
|
|
// Add to outgoing queue
|
|
s.outgoingQueue = append(s.outgoingQueue, protocolPacket)
|
|
|
|
return nil
|
|
}
|
|
|
|
// sendSessionResponse sends a session response
|
|
func (s *Stream) sendSessionResponse() error {
|
|
resp := &SessionResponse{
|
|
Session: s.sessionID,
|
|
Key: s.key,
|
|
Format: 0,
|
|
MaxLength: s.maxLength,
|
|
}
|
|
|
|
if s.compressed {
|
|
resp.Format |= 0x01
|
|
}
|
|
if s.encoded {
|
|
resp.Format |= 0x04
|
|
}
|
|
|
|
// Build response packet
|
|
data := make([]byte, 21)
|
|
binary.BigEndian.PutUint16(data[0:2], uint16(OPSessionResponse))
|
|
binary.LittleEndian.PutUint32(data[2:6], resp.Session)
|
|
binary.LittleEndian.PutUint32(data[6:10], resp.Key)
|
|
data[10] = resp.UnknownA
|
|
data[11] = resp.Format
|
|
data[12] = resp.UnknownB
|
|
binary.LittleEndian.PutUint32(data[13:17], resp.MaxLength)
|
|
binary.LittleEndian.PutUint32(data[17:21], resp.UnknownD)
|
|
|
|
// Session response doesn't have CRC
|
|
return s.sendRaw(data)
|
|
}
|
|
|
|
// sendKeepAliveResponse sends a keep-alive response
|
|
func (s *Stream) sendKeepAliveResponse() error {
|
|
data := make([]byte, 2)
|
|
binary.BigEndian.PutUint16(data, uint16(OPKeepAlive))
|
|
return s.sendWithCRC(data)
|
|
}
|
|
|
|
// sendAck sends an acknowledgment
|
|
func (s *Stream) sendAck(sequence uint16) error {
|
|
data := make([]byte, 4)
|
|
binary.BigEndian.PutUint16(data[0:2], uint16(OPAck))
|
|
binary.BigEndian.PutUint16(data[2:4], sequence)
|
|
return s.sendWithCRC(data)
|
|
}
|
|
|
|
// sendOutOfOrderAck sends an out-of-order acknowledgment
|
|
func (s *Stream) sendOutOfOrderAck(sequence uint16) error {
|
|
data := make([]byte, 4)
|
|
binary.BigEndian.PutUint16(data[0:2], uint16(OPOutOfOrderAck))
|
|
binary.BigEndian.PutUint16(data[2:4], sequence)
|
|
return s.sendWithCRC(data)
|
|
}
|
|
|
|
// sendWithCRC sends data with CRC
|
|
func (s *Stream) sendWithCRC(data []byte) error {
|
|
// Calculate CRC
|
|
crc := CalculateCRC16(data, s.key)
|
|
|
|
// Append CRC
|
|
fullData := make([]byte, len(data)+2)
|
|
copy(fullData, data)
|
|
binary.BigEndian.PutUint16(fullData[len(data):], crc)
|
|
|
|
return s.sendRaw(fullData)
|
|
}
|
|
|
|
// sendRaw sends raw data (should be called from factory)
|
|
func (s *Stream) sendRaw(data []byte) error {
|
|
s.lastSendTime = time.Now()
|
|
s.sentPackets++
|
|
// The actual sending will be done by StreamFactory
|
|
return nil
|
|
}
|
|
|
|
// resendPacket resends a packet
|
|
func (s *Stream) resendPacket(packet *ProtocolPacket) {
|
|
packet.AttemptCount++
|
|
packet.SentTime = int32(time.Now().Unix())
|
|
// Re-queue for sending
|
|
s.outgoingQueue = append(s.outgoingQueue, packet)
|
|
}
|
|
|
|
// updateRTT updates the round-trip time statistics
|
|
func (s *Stream) updateRTT(rtt time.Duration) {
|
|
if s.minRoundTrip == 0 || rtt < s.minRoundTrip {
|
|
s.minRoundTrip = rtt
|
|
}
|
|
if rtt > s.maxRoundTrip {
|
|
s.maxRoundTrip = rtt
|
|
}
|
|
|
|
// Update average (simple moving average)
|
|
if s.avgRoundTrip == 0 {
|
|
s.avgRoundTrip = rtt
|
|
} else {
|
|
s.avgRoundTrip = (s.avgRoundTrip*3 + rtt) / 4
|
|
}
|
|
|
|
// Update retransmit timeout
|
|
s.retransmitTimeout = s.avgRoundTrip * 3
|
|
if s.retransmitTimeout > 5*time.Second {
|
|
s.retransmitTimeout = 5 * time.Second
|
|
}
|
|
}
|
|
|
|
// SetOnPacket sets the packet callback
|
|
func (s *Stream) SetOnPacket(callback func(*ApplicationPacket)) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.onPacket = callback
|
|
}
|
|
|
|
// SetOnDisconnect sets the disconnect callback
|
|
func (s *Stream) SetOnDisconnect(callback func()) {
|
|
s.mu.Lock()
|
|
defer s.mu.Unlock()
|
|
s.onDisconnect = callback
|
|
} |