update packets to use opcodes
This commit is contained in:
parent
868ab62573
commit
ef7dd9d4ef
@ -2,63 +2,61 @@ package packets
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
|
"git.sharkk.net/EQ2/Protocol/opcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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
|
||||||
|
|
||||||
// EmuOpcode represents an emulator opcode
|
|
||||||
type EmuOpcode uint16
|
|
||||||
|
|
||||||
// Common emulator opcodes
|
|
||||||
const (
|
|
||||||
OpUnknown EmuOpcode = 0
|
|
||||||
// Add other opcodes as needed from op_codes.h
|
|
||||||
)
|
|
||||||
|
|
||||||
// AppPacket handles high-level game opcodes and application data
|
// AppPacket handles high-level game opcodes and application data
|
||||||
// This is the main packet type used by game logic
|
// This is the main packet type used by game logic
|
||||||
type AppPacket struct {
|
type AppPacket struct {
|
||||||
*Packet
|
*Packet
|
||||||
|
|
||||||
emuOpcode EmuOpcode // Cached emulator opcode
|
emuOpcode opcodes.EmuOpcode // Cached emulator opcode
|
||||||
opcodeSize uint8 // Size of opcode in bytes (1 or 2)
|
opcodeSize uint8 // Size of opcode in bytes (1 or 2)
|
||||||
|
manager opcodes.Manager // Opcode manager for translation
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppPacket creates a new application packet
|
// NewAppPacket creates a new application packet
|
||||||
func NewAppPacket() *AppPacket {
|
func NewAppPacket(manager opcodes.Manager) *AppPacket {
|
||||||
return &AppPacket{
|
return &AppPacket{
|
||||||
Packet: NewPacket(0, nil),
|
Packet: NewPacket(0, nil),
|
||||||
emuOpcode: OpUnknown,
|
emuOpcode: opcodes.OP_Unknown,
|
||||||
opcodeSize: DefaultOpcodeSize,
|
opcodeSize: DefaultOpcodeSize,
|
||||||
|
manager: manager,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppPacketWithOp creates a new application packet with opcode
|
// NewAppPacketWithOp creates a new application packet with opcode
|
||||||
func NewAppPacketWithOp(op EmuOpcode) *AppPacket {
|
func NewAppPacketWithOp(op opcodes.EmuOpcode, manager opcodes.Manager) *AppPacket {
|
||||||
app := &AppPacket{
|
app := &AppPacket{
|
||||||
Packet: NewPacket(0, nil),
|
Packet: NewPacket(0, nil),
|
||||||
opcodeSize: DefaultOpcodeSize,
|
opcodeSize: DefaultOpcodeSize,
|
||||||
|
manager: manager,
|
||||||
}
|
}
|
||||||
app.SetOpcode(op)
|
app.SetOpcode(op)
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppPacketWithSize creates a new application packet with opcode and size
|
// NewAppPacketWithSize creates a new application packet with opcode and size
|
||||||
func NewAppPacketWithSize(op EmuOpcode, size uint32) *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)),
|
||||||
opcodeSize: DefaultOpcodeSize,
|
opcodeSize: DefaultOpcodeSize,
|
||||||
|
manager: manager,
|
||||||
}
|
}
|
||||||
app.SetOpcode(op)
|
app.SetOpcode(op)
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewAppPacketWithData creates a new application packet with opcode and data
|
// NewAppPacketWithData creates a new application packet with opcode and data
|
||||||
func NewAppPacketWithData(op EmuOpcode, data []byte) *AppPacket {
|
func NewAppPacketWithData(op opcodes.EmuOpcode, data []byte, manager opcodes.Manager) *AppPacket {
|
||||||
app := &AppPacket{
|
app := &AppPacket{
|
||||||
Packet: NewPacket(0, data),
|
Packet: NewPacket(0, data),
|
||||||
opcodeSize: DefaultOpcodeSize,
|
opcodeSize: DefaultOpcodeSize,
|
||||||
|
manager: manager,
|
||||||
}
|
}
|
||||||
app.SetOpcode(op)
|
app.SetOpcode(op)
|
||||||
return app
|
return app
|
||||||
@ -66,7 +64,7 @@ func NewAppPacketWithData(op EmuOpcode, data []byte) *AppPacket {
|
|||||||
|
|
||||||
// NewAppPacketFromRaw creates app packet from raw buffer (used by ProtoPacket)
|
// NewAppPacketFromRaw creates app packet from raw buffer (used by ProtoPacket)
|
||||||
// Assumes first bytes are opcode based on opcodeSize
|
// Assumes first bytes are opcode based on opcodeSize
|
||||||
func NewAppPacketFromRaw(buf []byte, opcodeSize uint8) *AppPacket {
|
func NewAppPacketFromRaw(buf []byte, opcodeSize uint8, manager opcodes.Manager) *AppPacket {
|
||||||
if opcodeSize == 0 {
|
if opcodeSize == 0 {
|
||||||
opcodeSize = DefaultOpcodeSize
|
opcodeSize = DefaultOpcodeSize
|
||||||
}
|
}
|
||||||
@ -74,6 +72,7 @@ func NewAppPacketFromRaw(buf []byte, opcodeSize uint8) *AppPacket {
|
|||||||
app := &AppPacket{
|
app := &AppPacket{
|
||||||
Packet: NewPacket(0, nil),
|
Packet: NewPacket(0, nil),
|
||||||
opcodeSize: opcodeSize,
|
opcodeSize: opcodeSize,
|
||||||
|
manager: manager,
|
||||||
}
|
}
|
||||||
|
|
||||||
if opcodeSize == 1 && len(buf) >= 1 {
|
if opcodeSize == 1 && len(buf) >= 1 {
|
||||||
@ -90,6 +89,11 @@ func NewAppPacketFromRaw(buf []byte, opcodeSize uint8) *AppPacket {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert EQ opcode to emulator opcode
|
||||||
|
if app.manager != nil {
|
||||||
|
app.emuOpcode = app.manager.EQToEmu(app.Opcode)
|
||||||
|
}
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,29 +107,50 @@ func (a *AppPacket) SetOpcodeSize(size uint8) {
|
|||||||
a.opcodeSize = size
|
a.opcodeSize = size
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOpcode sets the emulator opcode
|
// SetManager sets the opcode manager for translation
|
||||||
func (a *AppPacket) SetOpcode(op EmuOpcode) {
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOpcode sets the emulator opcode and translates to EQ opcode
|
||||||
|
func (a *AppPacket) SetOpcode(op opcodes.EmuOpcode) {
|
||||||
a.emuOpcode = op
|
a.emuOpcode = op
|
||||||
// In full implementation, this would convert to protocol opcode
|
if a.manager != nil {
|
||||||
// using opcode manager. For now, direct assignment
|
a.Opcode = a.manager.EmuToEQ(op)
|
||||||
|
} 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 (with caching)
|
||||||
func (a *AppPacket) GetOpcode() EmuOpcode {
|
func (a *AppPacket) GetOpcode() opcodes.EmuOpcode {
|
||||||
if a.emuOpcode == OpUnknown {
|
if a.emuOpcode == opcodes.OP_Unknown && a.manager != nil {
|
||||||
// In full implementation, convert from protocol opcode
|
// Convert from protocol opcode
|
||||||
a.emuOpcode = EmuOpcode(a.Opcode)
|
a.emuOpcode = a.manager.EQToEmu(a.Opcode)
|
||||||
}
|
}
|
||||||
return a.emuOpcode
|
return a.emuOpcode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOpcodeName returns the name of the current opcode
|
||||||
|
func (a *AppPacket) GetOpcodeName() string {
|
||||||
|
if a.manager != nil {
|
||||||
|
return a.manager.EmuToName(a.GetOpcode())
|
||||||
|
}
|
||||||
|
return "OP_Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
// Copy creates a deep copy of this application packet
|
// Copy creates a deep copy of this application packet
|
||||||
func (a *AppPacket) Copy() *AppPacket {
|
func (a *AppPacket) Copy() *AppPacket {
|
||||||
newApp := &AppPacket{
|
newApp := &AppPacket{
|
||||||
Packet: NewPacket(a.Opcode, a.Buffer),
|
Packet: NewPacket(a.Opcode, a.Buffer),
|
||||||
emuOpcode: a.emuOpcode,
|
emuOpcode: a.emuOpcode,
|
||||||
opcodeSize: a.opcodeSize,
|
opcodeSize: a.opcodeSize,
|
||||||
|
manager: a.manager,
|
||||||
}
|
}
|
||||||
newApp.Packet.CopyInfo(a.Packet)
|
newApp.Packet.CopyInfo(a.Packet)
|
||||||
return newApp
|
return newApp
|
||||||
|
@ -1,39 +1,221 @@
|
|||||||
package packets
|
package packets
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"bytes"
|
||||||
|
"compress/zlib"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"hash/crc32"
|
||||||
|
"io"
|
||||||
|
|
||||||
// ValidateCRC validates packet CRC
|
"git.sharkk.net/EQ2/Protocol/crypto"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateCRC validates packet CRC using EQ's CRC32 implementation
|
||||||
func ValidateCRC(buffer []byte, key uint32) bool {
|
func ValidateCRC(buffer []byte, key uint32) bool {
|
||||||
// TODO: Implement CRC validation
|
if len(buffer) < 4 {
|
||||||
return true
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compress compresses packet data using zlib
|
// Extract CRC from last 4 bytes
|
||||||
|
packetCRC := binary.BigEndian.Uint32(buffer[len(buffer)-4:])
|
||||||
|
|
||||||
|
// Calculate CRC on data portion (excluding CRC bytes)
|
||||||
|
data := buffer[:len(buffer)-4]
|
||||||
|
calculatedCRC := CalculateCRC(data, key)
|
||||||
|
|
||||||
|
return packetCRC == calculatedCRC
|
||||||
|
}
|
||||||
|
|
||||||
|
// CalculateCRC calculates CRC32 for packet data
|
||||||
|
func CalculateCRC(data []byte, key uint32) uint32 {
|
||||||
|
// EQ uses standard CRC32 with XOR key
|
||||||
|
crc := crc32.ChecksumIEEE(data)
|
||||||
|
return crc ^ key
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compress compresses packet data using zlib (matches EQ compression)
|
||||||
func Compress(src []byte) ([]byte, error) {
|
func Compress(src []byte) ([]byte, error) {
|
||||||
// TODO: Implement zlib compression
|
if len(src) == 0 {
|
||||||
return src, nil
|
return src, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
// EQ uses default compression level
|
||||||
|
w := zlib.NewWriter(&buf)
|
||||||
|
|
||||||
|
// Write uncompressed length first (4 bytes) - EQ protocol requirement
|
||||||
|
uncompressedLen := uint32(len(src))
|
||||||
|
if err := binary.Write(&buf, binary.BigEndian, uncompressedLen); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compress the data
|
||||||
|
if _, err := w.Write(src); err != nil {
|
||||||
|
w.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := w.Close(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Decompress decompresses packet data using zlib
|
// Decompress decompresses packet data using zlib
|
||||||
func Decompress(src []byte) ([]byte, error) {
|
func Decompress(src []byte) ([]byte, error) {
|
||||||
// TODO: Implement zlib decompression
|
if len(src) < 4 {
|
||||||
return src, nil
|
return nil, fmt.Errorf("compressed data too small")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChatEncode encodes chat data
|
// Read uncompressed length (first 4 bytes)
|
||||||
|
uncompressedLen := binary.BigEndian.Uint32(src[:4])
|
||||||
|
|
||||||
|
// Sanity check - prevent decompression bombs
|
||||||
|
if uncompressedLen > MaxPacketSize {
|
||||||
|
return nil, fmt.Errorf("uncompressed size %d exceeds max packet size", uncompressedLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create reader for compressed data (skip length prefix)
|
||||||
|
r, err := zlib.NewReader(bytes.NewReader(src[4:]))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Close()
|
||||||
|
|
||||||
|
// Read decompressed data
|
||||||
|
decompressed := make([]byte, uncompressedLen)
|
||||||
|
if _, err := io.ReadFull(r, decompressed); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return decompressed, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
// TODO: Implement chat encoding
|
if len(buffer) == 0 || encodeKey == 0 {
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChatDecode decodes chat data
|
// EQ chat encoding algorithm
|
||||||
|
key := byte(encodeKey & 0xFF)
|
||||||
|
for i := range buffer {
|
||||||
|
// XOR with rotating key based on position
|
||||||
|
buffer[i] ^= key
|
||||||
|
// Rotate key for next byte
|
||||||
|
key = ((key << 1) | (key >> 7)) & 0xFF
|
||||||
|
// Add position-based variation
|
||||||
|
if i%3 == 0 {
|
||||||
|
key ^= byte(i & 0xFF)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChatDecode decodes chat data using EQ's XOR-based encoding
|
||||||
|
// Decoding is the same as encoding for XOR
|
||||||
func ChatDecode(buffer []byte, decodeKey int) {
|
func ChatDecode(buffer []byte, decodeKey int) {
|
||||||
// TODO: Implement chat decoding
|
// XOR encoding is symmetric - encode and decode are the same operation
|
||||||
|
ChatEncode(buffer, decodeKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, trimCRC bool) bool {
|
||||||
// TODO: Implement protocol packet validation
|
if len(buffer) < 2 {
|
||||||
return len(buffer) >= 2
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for valid protocol opcodes
|
||||||
|
opcode := binary.BigEndian.Uint16(buffer[:2])
|
||||||
|
|
||||||
|
// Protocol opcodes from protocol.go
|
||||||
|
validOpcodes := map[uint16]bool{
|
||||||
|
0x0001: true, // OP_SessionRequest
|
||||||
|
0x0002: true, // OP_SessionResponse
|
||||||
|
0x0003: true, // OP_Combined
|
||||||
|
0x0005: true, // OP_SessionDisconnect
|
||||||
|
0x0006: true, // OP_KeepAlive
|
||||||
|
0x0007: true, // OP_SessionStatRequest
|
||||||
|
0x0008: true, // OP_SessionStatResponse
|
||||||
|
0x0009: true, // OP_Packet
|
||||||
|
0x000d: true, // OP_Fragment
|
||||||
|
0x0015: true, // OP_Ack
|
||||||
|
0x0019: true, // OP_AppCombined
|
||||||
|
0x001d: true, // OP_OutOfOrderAck
|
||||||
|
0x001e: true, // OP_OutOfSession
|
||||||
|
}
|
||||||
|
|
||||||
|
if !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
|
// Helper function to convert uint32 IP to string
|
||||||
@ -41,3 +223,20 @@ func longToIP(ip uint32) string {
|
|||||||
return fmt.Sprintf("%d.%d.%d.%d",
|
return fmt.Sprintf("%d.%d.%d.%d",
|
||||||
byte(ip>>24), byte(ip>>16), byte(ip>>8), byte(ip))
|
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]
|
||||||
|
}
|
||||||
|
@ -3,6 +3,8 @@ package packets
|
|||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"git.sharkk.net/EQ2/Protocol/opcodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProtoPacket handles low-level protocol features including EQ2-specific operations
|
// ProtoPacket handles low-level protocol features including EQ2-specific operations
|
||||||
@ -14,12 +16,19 @@ type ProtoPacket struct {
|
|||||||
flags uint8 // bit 0: compressed, bit 1: prepared, bit 2: encrypted, bit 3: acked
|
flags uint8 // bit 0: compressed, bit 1: prepared, bit 2: encrypted, bit 3: acked
|
||||||
|
|
||||||
// EQ2-specific
|
// EQ2-specific
|
||||||
LoginOp EmuOpcode // From EQ2Packet
|
LoginOp opcodes.EmuOpcode // From EQ2Packet
|
||||||
|
|
||||||
// Reliability and sequencing
|
// Reliability and sequencing
|
||||||
Sequence int32
|
Sequence int32
|
||||||
SentTime int32
|
SentTime int32
|
||||||
AttemptCount int8
|
AttemptCount int8
|
||||||
|
|
||||||
|
// Opcode manager for translation
|
||||||
|
manager opcodes.Manager
|
||||||
|
|
||||||
|
// Compression/encoding settings
|
||||||
|
CompressThreshold int
|
||||||
|
EncodeKey int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Protocol flag constants
|
// Protocol flag constants
|
||||||
@ -30,15 +39,20 @@ const (
|
|||||||
FlagAcked
|
FlagAcked
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Default compression threshold (compress packets larger than this)
|
||||||
|
const DefaultCompressThreshold = 100
|
||||||
|
|
||||||
// NewProtoPacket creates a protocol packet with opcode and buffer
|
// NewProtoPacket creates a protocol packet with opcode and buffer
|
||||||
func NewProtoPacket(op uint16, buf []byte) *ProtoPacket {
|
func NewProtoPacket(op uint16, buf []byte, manager opcodes.Manager) *ProtoPacket {
|
||||||
return &ProtoPacket{
|
return &ProtoPacket{
|
||||||
Packet: NewPacket(op, buf),
|
Packet: NewPacket(op, buf),
|
||||||
|
manager: manager,
|
||||||
|
CompressThreshold: DefaultCompressThreshold,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewProtoPacketFromRaw creates a protocol packet from raw buffer
|
// NewProtoPacketFromRaw creates a protocol packet from raw buffer
|
||||||
func NewProtoPacketFromRaw(buf []byte, opcodeOverride int) *ProtoPacket {
|
func NewProtoPacketFromRaw(buf []byte, opcodeOverride int, manager opcodes.Manager) *ProtoPacket {
|
||||||
var offset uint32
|
var offset uint32
|
||||||
var opcode uint16
|
var opcode uint16
|
||||||
|
|
||||||
@ -55,13 +69,27 @@ func NewProtoPacketFromRaw(buf []byte, opcodeOverride int) *ProtoPacket {
|
|||||||
copy(data, buf[offset:])
|
copy(data, buf[offset:])
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ProtoPacket{
|
pp := &ProtoPacket{
|
||||||
Packet: &Packet{
|
Packet: &Packet{
|
||||||
Opcode: opcode,
|
Opcode: opcode,
|
||||||
Buffer: data,
|
Buffer: data,
|
||||||
Timestamp: time.Now(),
|
Timestamp: time.Now(),
|
||||||
},
|
},
|
||||||
|
manager: manager,
|
||||||
|
CompressThreshold: DefaultCompressThreshold,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Convert EQ opcode to emulator opcode if manager available
|
||||||
|
if pp.manager != nil {
|
||||||
|
pp.LoginOp = pp.manager.EQToEmu(opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return pp
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetManager sets the opcode manager for translation
|
||||||
|
func (p *ProtoPacket) SetManager(manager opcodes.Manager) {
|
||||||
|
p.manager = manager
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsCompressed returns true if packet is compressed
|
// IsCompressed returns true if packet is compressed
|
||||||
@ -120,6 +148,64 @@ func (p *ProtoPacket) SetAcked(acked bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 IsChatPacket(p.Opcode) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
// Copy creates a deep copy of this protocol packet
|
// Copy creates a deep copy of this protocol packet
|
||||||
func (p *ProtoPacket) Copy() *ProtoPacket {
|
func (p *ProtoPacket) Copy() *ProtoPacket {
|
||||||
newPacket := &ProtoPacket{
|
newPacket := &ProtoPacket{
|
||||||
@ -129,6 +215,9 @@ func (p *ProtoPacket) Copy() *ProtoPacket {
|
|||||||
Sequence: p.Sequence,
|
Sequence: p.Sequence,
|
||||||
SentTime: p.SentTime,
|
SentTime: p.SentTime,
|
||||||
AttemptCount: p.AttemptCount,
|
AttemptCount: p.AttemptCount,
|
||||||
|
manager: p.manager,
|
||||||
|
CompressThreshold: p.CompressThreshold,
|
||||||
|
EncodeKey: p.EncodeKey,
|
||||||
}
|
}
|
||||||
newPacket.Packet.CopyInfo(p.Packet)
|
newPacket.Packet.CopyInfo(p.Packet)
|
||||||
return newPacket
|
return newPacket
|
||||||
@ -136,26 +225,156 @@ func (p *ProtoPacket) Copy() *ProtoPacket {
|
|||||||
|
|
||||||
// Serialize writes the protocol packet to a destination buffer
|
// Serialize writes the protocol packet to a destination buffer
|
||||||
func (p *ProtoPacket) Serialize(dest []byte, offset int8) uint32 {
|
func (p *ProtoPacket) Serialize(dest []byte, offset int8) uint32 {
|
||||||
|
// 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)
|
// Write opcode (2 bytes)
|
||||||
if p.Opcode > 0xff {
|
if p.Opcode > 0xff {
|
||||||
binary.BigEndian.PutUint16(dest, p.Opcode)
|
binary.BigEndian.PutUint16(dest[pos:], p.Opcode)
|
||||||
} else {
|
} else {
|
||||||
dest[0] = 0
|
dest[pos] = 0
|
||||||
dest[1] = byte(p.Opcode)
|
dest[pos+1] = byte(p.Opcode)
|
||||||
}
|
}
|
||||||
|
pos += 2
|
||||||
|
|
||||||
// Copy packet data after opcode
|
// Copy packet data after opcode
|
||||||
if offset < int8(len(p.Buffer)) {
|
if offset < int8(len(p.Buffer)) {
|
||||||
copy(dest[2:], p.Buffer[offset:])
|
copy(dest[pos:], p.Buffer[offset:])
|
||||||
}
|
}
|
||||||
|
|
||||||
return uint32(len(p.Buffer)-int(offset)) + 2
|
return uint32(len(p.Buffer)-int(offset)) + uint32(pos)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PreparePacket prepares an EQ2 packet for transmission (from EQ2Packet)
|
||||||
|
func (p *ProtoPacket) PreparePacket(maxLen int16) int8 {
|
||||||
|
if p.IsPrepared() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
p.SetPrepared(true)
|
||||||
|
|
||||||
|
// Apply compression if needed
|
||||||
|
if err := p.CompressPacket(); err != nil {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply chat encoding if needed
|
||||||
|
p.EncodeChat()
|
||||||
|
|
||||||
|
// Convert emulator opcode to network opcode using manager
|
||||||
|
var loginOpcode uint16
|
||||||
|
if p.manager != nil {
|
||||||
|
loginOpcode = p.manager.EmuToEQ(p.LoginOp)
|
||||||
|
} else {
|
||||||
|
loginOpcode = uint16(p.LoginOp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset int8
|
||||||
|
newSize := len(p.Buffer) + 2 + 1 // sequence(2) + compressed_flag(1)
|
||||||
|
oversized := false
|
||||||
|
|
||||||
|
// Add compression flag space if compressed
|
||||||
|
if p.IsCompressed() {
|
||||||
|
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
|
||||||
|
newBuffer := make([]byte, newSize)
|
||||||
|
ptr := 2 // Skip sequence field
|
||||||
|
|
||||||
|
// Add compression flag if needed
|
||||||
|
if p.IsCompressed() {
|
||||||
|
newBuffer[ptr] = 0x5a // EQ compression flag
|
||||||
|
ptr++
|
||||||
|
}
|
||||||
|
|
||||||
|
if loginOpcode != 2 {
|
||||||
|
if oversized {
|
||||||
|
newBuffer[ptr] = 0xff // Oversized marker
|
||||||
|
ptr++
|
||||||
|
binary.BigEndian.PutUint16(newBuffer[ptr:], loginOpcode)
|
||||||
|
ptr += 2
|
||||||
|
} else {
|
||||||
|
binary.BigEndian.PutUint16(newBuffer[ptr:], loginOpcode)
|
||||||
|
ptr += 2
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newBuffer[ptr] = byte(loginOpcode)
|
||||||
|
ptr++
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy original packet data
|
||||||
|
copy(newBuffer[ptr:], p.Buffer)
|
||||||
|
|
||||||
|
p.Buffer = newBuffer
|
||||||
|
offset = int8(newSize - len(p.Buffer) - 1)
|
||||||
|
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeApplicationPacket converts protocol packet to application packet
|
||||||
|
func (p *ProtoPacket) MakeApplicationPacket(opcodeSize uint8) *AppPacket {
|
||||||
|
// Decompress if needed
|
||||||
|
p.DecompressPacket()
|
||||||
|
|
||||||
|
// Decode chat if needed
|
||||||
|
p.DecodeChat()
|
||||||
|
|
||||||
|
if opcodeSize == 0 {
|
||||||
|
opcodeSize = DefaultOpcodeSize
|
||||||
|
}
|
||||||
|
|
||||||
|
app := &AppPacket{
|
||||||
|
Packet: NewPacket(0, p.Buffer),
|
||||||
|
opcodeSize: opcodeSize,
|
||||||
|
manager: p.manager,
|
||||||
|
}
|
||||||
|
app.CopyInfo(p.Packet)
|
||||||
|
|
||||||
|
// Parse opcode from buffer based on size
|
||||||
|
if opcodeSize == 1 && len(p.Buffer) >= 1 {
|
||||||
|
app.Opcode = uint16(p.Buffer[0])
|
||||||
|
app.Buffer = p.Buffer[1:]
|
||||||
|
} 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
|
||||||
|
if app.manager != nil {
|
||||||
|
app.emuOpcode = app.manager.EQToEmu(app.Opcode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
// AppCombine combines this packet with another for efficient transmission (from EQ2Packet)
|
// AppCombine combines this packet with another for efficient transmission (from EQ2Packet)
|
||||||
func (p *ProtoPacket) AppCombine(rhs *ProtoPacket) bool {
|
func (p *ProtoPacket) AppCombine(rhs *ProtoPacket) bool {
|
||||||
const opAppCombined = 0x19 // OP_AppCombined value
|
const opAppCombined = 0x19 // OP_AppCombined value
|
||||||
|
|
||||||
|
// Apply compression to both packets before combining
|
||||||
|
p.CompressPacket()
|
||||||
|
rhs.CompressPacket()
|
||||||
|
|
||||||
// Case 1: This packet is already a combined packet
|
// Case 1: This packet is already a combined packet
|
||||||
if p.Opcode == opAppCombined && (len(p.Buffer)+len(rhs.Buffer)+3) < 255 {
|
if p.Opcode == opAppCombined && (len(p.Buffer)+len(rhs.Buffer)+3) < 255 {
|
||||||
tmpSize := len(rhs.Buffer) - 2
|
tmpSize := len(rhs.Buffer) - 2
|
||||||
@ -195,87 +414,6 @@ func (p *ProtoPacket) AppCombine(rhs *ProtoPacket) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Case 2: Create new combined packet
|
// Case 2: Create new combined packet (simplified)
|
||||||
// Implementation would continue here for other combine cases
|
|
||||||
// Simplified for brevity
|
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// PreparePacket prepares an EQ2 packet for transmission (from EQ2Packet)
|
|
||||||
func (p *ProtoPacket) PreparePacket(maxLen int16) int8 {
|
|
||||||
if p.IsPrepared() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
p.SetPrepared(true)
|
|
||||||
|
|
||||||
// Convert emulator opcode to network opcode
|
|
||||||
// This would use opcode manager in full implementation
|
|
||||||
loginOpcode := p.LoginOp // Simplified - would do actual conversion
|
|
||||||
|
|
||||||
var offset int8
|
|
||||||
newSize := len(p.Buffer) + 2 + 1 // sequence(2) + compressed_flag(1)
|
|
||||||
oversized := false
|
|
||||||
|
|
||||||
// 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
|
|
||||||
newBuffer := make([]byte, newSize)
|
|
||||||
ptr := 2 // Skip sequence field
|
|
||||||
|
|
||||||
if loginOpcode != 2 {
|
|
||||||
if oversized {
|
|
||||||
ptr++ // Skip compressed flag position
|
|
||||||
newBuffer[ptr] = 0xff // Oversized marker
|
|
||||||
ptr++
|
|
||||||
binary.BigEndian.PutUint16(newBuffer[ptr:], uint16(loginOpcode))
|
|
||||||
ptr += 2
|
|
||||||
} else {
|
|
||||||
binary.BigEndian.PutUint16(newBuffer[ptr:], uint16(loginOpcode))
|
|
||||||
ptr += 2
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
newBuffer[ptr] = byte(loginOpcode)
|
|
||||||
ptr++
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy original packet data
|
|
||||||
copy(newBuffer[ptr:], p.Buffer)
|
|
||||||
|
|
||||||
p.Buffer = newBuffer
|
|
||||||
offset = int8(newSize - len(p.Buffer) - 1)
|
|
||||||
|
|
||||||
return offset
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeApplicationPacket converts protocol packet to application packet
|
|
||||||
func (p *ProtoPacket) MakeApplicationPacket(opcodeSize uint8) *AppPacket {
|
|
||||||
if opcodeSize == 0 {
|
|
||||||
opcodeSize = DefaultOpcodeSize
|
|
||||||
}
|
|
||||||
|
|
||||||
app := &AppPacket{
|
|
||||||
Packet: NewPacket(0, p.Buffer),
|
|
||||||
opcodeSize: opcodeSize,
|
|
||||||
}
|
|
||||||
app.CopyInfo(p.Packet)
|
|
||||||
|
|
||||||
// Parse opcode from buffer based on size
|
|
||||||
if opcodeSize == 1 && len(p.Buffer) >= 1 {
|
|
||||||
app.Opcode = uint16(p.Buffer[0])
|
|
||||||
app.Buffer = p.Buffer[1:]
|
|
||||||
} else if len(p.Buffer) >= 2 {
|
|
||||||
app.Opcode = binary.BigEndian.Uint16(p.Buffer[:2])
|
|
||||||
app.Buffer = p.Buffer[2:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return app
|
|
||||||
}
|
|
||||||
|
@ -7,14 +7,6 @@ type PacketSerializer interface {
|
|||||||
Copy() PacketSerializer
|
Copy() PacketSerializer
|
||||||
}
|
}
|
||||||
|
|
||||||
// OpcodeManager interface for opcode translation
|
|
||||||
type OpcodeManager interface {
|
|
||||||
EmuToEQ(emu EmuOpcode) uint16
|
|
||||||
EQToEmu(eq uint16) EmuOpcode
|
|
||||||
EmuToName(emu EmuOpcode) string
|
|
||||||
EQToName(eq uint16) string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stream states
|
// Stream states
|
||||||
type StreamState uint8
|
type StreamState uint8
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user