fix chat encoding
This commit is contained in:
parent
1a9b7effa6
commit
4ecdcc868f
@ -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]
|
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user