1
0

fix chat encoding

This commit is contained in:
Sky Johnson 2025-09-03 20:48:08 -05:00
parent 1a9b7effa6
commit 4ecdcc868f
2 changed files with 139 additions and 33 deletions

View File

@ -8,22 +8,38 @@ import (
"io" "io"
"git.sharkk.net/EQ2/Protocol/crypto" "git.sharkk.net/EQ2/Protocol/crypto"
"git.sharkk.net/EQ2/Protocol/opcodes"
) )
// ValidateCRC validates packet CRC using EQ2's custom CRC16 // ValidateCRC validates packet CRC using EQ2's custom CRC16 (matches C++ EQProtocolPacket::ValidateCRC)
func ValidateCRC(buffer []byte, key uint32) bool { func ValidateCRC(buffer []byte, key uint32) bool {
if len(buffer) < 2 { if len(buffer) < 2 {
return false return false
} }
// Extract CRC from last 2 bytes (EQ2 uses CRC16) // Session packets are not CRC protected
if len(buffer) >= 2 && buffer[0] == 0x00 &&
(buffer[1] == byte(opcodes.OP_SessionRequest) ||
buffer[1] == byte(opcodes.OP_SessionResponse) ||
buffer[1] == byte(opcodes.OP_OutOfSession)) {
return true
}
// Combined application packets are also exempt (OP_AppCombined = 0x0019)
if len(buffer) >= 4 && buffer[2] == 0x00 && buffer[3] == 0x19 {
return true
}
// All other packets must have valid CRC
// Extract CRC from last 2 bytes (network byte order)
packetCRC := binary.BigEndian.Uint16(buffer[len(buffer)-2:]) 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)-2] data := buffer[:len(buffer)-2]
calculatedCRC := crypto.CalculateCRC(data, key) calculatedCRC := crypto.CalculateCRC(data, key)
return packetCRC == calculatedCRC // Valid if no CRC present (packetCRC == 0) or CRCs match
return packetCRC == 0 || calculatedCRC == packetCRC
} }
// AppendCRC appends CRC16 to packet buffer using EQ2's custom CRC // AppendCRC appends CRC16 to packet buffer using EQ2's custom CRC
@ -214,27 +230,72 @@ func DecompressPacket(buffer []byte) ([]byte, error) {
return buffer, nil return buffer, nil
} }
// ChatEncode encodes chat data using EQ's XOR-based encoding // ChatEncode encodes chat data using EQ's XOR-based encoding (matches C++ EQProtocolPacket::ChatEncode)
// Uses 4-byte block XOR with rolling key that updates from encrypted data
func ChatEncode(buffer []byte, encodeKey int) { func ChatEncode(buffer []byte, encodeKey int) {
if len(buffer) == 0 || encodeKey == 0 { if len(buffer) <= 2 || encodeKey == 0 {
return return
} }
key := byte(encodeKey & 0xFF) // Skip encoding for certain packet types (matches C++ conditions)
for i := range buffer { if buffer[1] == 0x01 || buffer[0] == 0x02 || buffer[0] == 0x1d {
buffer[i] ^= key return
// Rotate key for next byte }
key = ((key << 1) | (key >> 7)) & 0xFF
// Add position-based variation // Skip first 2 bytes (opcode)
if i%3 == 0 { data := buffer[2:]
key ^= byte(i & 0xFF) size := len(data)
} key := int32(encodeKey)
// Encode 4-byte blocks with rolling key
i := 0
for ; i+4 <= size; i += 4 {
// Read 4 bytes as int32 (little-endian like C++)
pt := binary.LittleEndian.Uint32(data[i:i+4])
encrypted := pt ^ uint32(key)
key = int32(encrypted) // Update key with encrypted data
binary.LittleEndian.PutUint32(data[i:i+4], encrypted)
}
// Encode remaining bytes with last key byte
keyByte := byte(key & 0xFF)
for ; i < size; i++ {
data[i] ^= keyByte
} }
} }
// ChatDecode decodes chat data (XOR is symmetric) // ChatDecode decodes chat data using EQ's XOR-based encoding (matches C++ EQProtocolPacket::ChatDecode)
// Uses 4-byte block XOR with rolling key that updates from encrypted data
func ChatDecode(buffer []byte, decodeKey int) { func ChatDecode(buffer []byte, decodeKey int) {
ChatEncode(buffer, decodeKey) if len(buffer) <= 2 || decodeKey == 0 {
return
}
// Skip decoding for certain packet types (matches C++ conditions)
if buffer[1] == 0x01 || buffer[0] == 0x02 || buffer[0] == 0x1d {
return
}
// Skip first 2 bytes (opcode)
data := buffer[2:]
size := len(data)
key := int32(decodeKey)
// Decode 4-byte blocks with rolling key
i := 0
for ; i+4 <= size; i += 4 {
// Read 4 bytes as int32 (little-endian like C++)
encrypted := binary.LittleEndian.Uint32(data[i:i+4])
decrypted := encrypted ^ uint32(key)
key = int32(encrypted) // Update key with encrypted data (before decryption)
binary.LittleEndian.PutUint32(data[i:i+4], decrypted)
}
// Decode remaining bytes with last key byte
keyByte := byte(key & 0xFF)
for ; i < size; i++ {
data[i] ^= keyByte
}
} }
// IsChatPacket checks if opcode is a chat-related packet // IsChatPacket checks if opcode is a chat-related packet
@ -256,7 +317,7 @@ func longToIP(ip uint32) string {
byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip)) 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 (matches C++ EQProtocolPacket::IsProtocolPacket)
func IsProtocolPacket(buffer []byte) bool { func IsProtocolPacket(buffer []byte) bool {
if len(buffer) < 2 { if len(buffer) < 2 {
return false return false
@ -264,21 +325,23 @@ func IsProtocolPacket(buffer []byte) bool {
opcode := binary.BigEndian.Uint16(buffer[:2]) opcode := binary.BigEndian.Uint16(buffer[:2])
validOpcodes := map[uint16]bool{ // Check against known protocol opcodes
0x0001: true, // OP_SessionRequest switch opcode {
0x0002: true, // OP_SessionResponse case opcodes.OP_SessionRequest,
0x0003: true, // OP_Combined opcodes.OP_SessionResponse,
0x0005: true, // OP_SessionDisconnect opcodes.OP_Combined,
0x0006: true, // OP_KeepAlive opcodes.OP_SessionDisconnect,
0x0007: true, // OP_SessionStatRequest opcodes.OP_KeepAlive,
0x0008: true, // OP_SessionStatResponse opcodes.OP_SessionStatRequest,
0x0009: true, // OP_Packet opcodes.OP_SessionStatResponse,
0x000d: true, // OP_Fragment opcodes.OP_Packet,
0x0015: true, // OP_Ack opcodes.OP_Fragment,
0x0019: true, // OP_AppCombined opcodes.OP_Ack,
0x001d: true, // OP_OutOfOrderAck opcodes.OP_AppCombined,
0x001e: true, // OP_OutOfSession opcodes.OP_OutOfOrderAck,
opcodes.OP_OutOfSession:
return true
default:
return false
} }
return validOpcodes[opcode]
} }

View File

@ -2,6 +2,7 @@ package packets
import ( import (
"encoding/binary" "encoding/binary"
"fmt"
"time" "time"
"git.sharkk.net/EQ2/Protocol/crypto" "git.sharkk.net/EQ2/Protocol/crypto"
@ -516,3 +517,45 @@ func (p *ProtoPacket) Copy() *ProtoPacket {
newPacket.Packet.CopyInfo(p.Packet) newPacket.Packet.CopyInfo(p.Packet)
return newPacket return newPacket
} }
// GetOpcodeName returns human-readable name for the opcode (matches C++ EQ2Packet::GetOpcodeName)
func (p *ProtoPacket) GetOpcodeName() string {
// Use manager to get opcode name if available
if p.manager != nil {
if name := p.manager.EmuToName(p.LoginOp); name != "" {
return name
}
}
// Fall back to protocol opcode names
switch p.Opcode {
case opcodes.OP_SessionRequest:
return "OP_SessionRequest"
case opcodes.OP_SessionResponse:
return "OP_SessionResponse"
case opcodes.OP_Combined:
return "OP_Combined"
case opcodes.OP_SessionDisconnect:
return "OP_SessionDisconnect"
case opcodes.OP_KeepAlive:
return "OP_KeepAlive"
case opcodes.OP_SessionStatRequest:
return "OP_SessionStatRequest"
case opcodes.OP_SessionStatResponse:
return "OP_SessionStatResponse"
case opcodes.OP_Packet:
return "OP_Packet"
case opcodes.OP_Fragment:
return "OP_Fragment"
case opcodes.OP_Ack:
return "OP_Ack"
case opcodes.OP_AppCombined:
return "OP_AppCombined"
case opcodes.OP_OutOfOrderAck:
return "OP_OutOfOrderAck"
case opcodes.OP_OutOfSession:
return "OP_OutOfSession"
default:
return fmt.Sprintf("Unknown(0x%04X)", p.Opcode)
}
}