287 lines
7.0 KiB
Go
287 lines
7.0 KiB
Go
package eq2net
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
)
|
|
|
|
// AppPacket handles application-level game packets with opcode abstraction
|
|
// This layer sits above the protocol layer and handles game-specific opcodes
|
|
type AppPacket struct {
|
|
EQPacket
|
|
|
|
EmuOpcode uint16 // Emulator opcode (internal representation)
|
|
OpcodeSize uint8 // Size of opcode in bytes (1 or 2)
|
|
}
|
|
|
|
// Default opcode size for application packets
|
|
var DefaultAppOpcodeSize uint8 = 2
|
|
|
|
// NewAppPacket creates a new application packet with emulator opcode
|
|
func NewAppPacket(emuOpcode uint16, data []byte) *AppPacket {
|
|
p := &AppPacket{
|
|
EQPacket: *NewEQPacket(0, data), // Network opcode will be set during conversion
|
|
EmuOpcode: emuOpcode,
|
|
OpcodeSize: DefaultAppOpcodeSize,
|
|
}
|
|
return p
|
|
}
|
|
|
|
// NewAppPacketWithSize creates an application packet with specified opcode size
|
|
func NewAppPacketWithSize(emuOpcode uint16, data []byte, opcodeSize uint8) *AppPacket {
|
|
p := &AppPacket{
|
|
EQPacket: *NewEQPacket(0, data),
|
|
EmuOpcode: emuOpcode,
|
|
OpcodeSize: opcodeSize,
|
|
}
|
|
return p
|
|
}
|
|
|
|
// ParseAppPacket creates an application packet from raw data
|
|
// The data should NOT include the protocol header, just the app opcode + payload
|
|
func ParseAppPacket(data []byte, opcodeSize uint8) (*AppPacket, error) {
|
|
if len(data) < int(opcodeSize) {
|
|
return nil, fmt.Errorf("packet too small for opcode: need %d bytes, got %d", opcodeSize, len(data))
|
|
}
|
|
|
|
// Extract opcode
|
|
var opcode uint16
|
|
if opcodeSize == 1 {
|
|
opcode = uint16(data[0])
|
|
} else {
|
|
// Handle special encoding for 2-byte opcodes
|
|
if data[0] == 0x00 && len(data) > 2 {
|
|
// Special case: extra 0x00 prefix for opcodes with low byte = 0x00
|
|
opcode = binary.BigEndian.Uint16(data[1:3])
|
|
data = data[3:] // Skip the extra byte
|
|
} else {
|
|
opcode = binary.BigEndian.Uint16(data[0:2])
|
|
data = data[2:]
|
|
}
|
|
}
|
|
|
|
p := &AppPacket{
|
|
EQPacket: *NewEQPacket(opcode, data[opcodeSize:]),
|
|
EmuOpcode: opcode, // Initially same as network opcode
|
|
OpcodeSize: opcodeSize,
|
|
}
|
|
|
|
return p, nil
|
|
}
|
|
|
|
// SetEmuOpcode sets the emulator opcode
|
|
// This is used when converting between emulator and network opcodes
|
|
func (p *AppPacket) SetEmuOpcode(opcode uint16) {
|
|
p.EmuOpcode = opcode
|
|
}
|
|
|
|
// GetEmuOpcode returns the emulator opcode
|
|
func (p *AppPacket) GetEmuOpcode() uint16 {
|
|
return p.EmuOpcode
|
|
}
|
|
|
|
// SerializeApp serializes the application packet with proper opcode encoding
|
|
func (p *AppPacket) SerializeApp() []byte {
|
|
opcodeBytes := p.OpcodeSize
|
|
extraBytes := 0
|
|
|
|
// Handle special encoding rules for 2-byte opcodes
|
|
if p.OpcodeSize == 2 && (p.Opcode&0x00FF) == 0 {
|
|
// Opcodes with low byte = 0x00 need an extra 0x00 prefix
|
|
extraBytes = 1
|
|
}
|
|
|
|
// Create buffer
|
|
buf := make([]byte, int(opcodeBytes)+extraBytes+len(p.Buffer))
|
|
offset := 0
|
|
|
|
// Write opcode
|
|
if p.OpcodeSize == 1 {
|
|
buf[offset] = byte(p.Opcode)
|
|
offset++
|
|
} else {
|
|
if extraBytes > 0 {
|
|
// Special encoding: add 0x00 prefix
|
|
buf[offset] = 0x00
|
|
offset++
|
|
binary.BigEndian.PutUint16(buf[offset:], p.Opcode)
|
|
offset += 2
|
|
} else {
|
|
binary.BigEndian.PutUint16(buf[offset:], p.Opcode)
|
|
offset += 2
|
|
}
|
|
}
|
|
|
|
// Copy packet data
|
|
if len(p.Buffer) > 0 {
|
|
copy(buf[offset:], p.Buffer)
|
|
}
|
|
|
|
return buf
|
|
}
|
|
|
|
// Combine combines multiple application packets into a single packet
|
|
// Returns true if successful, false if size limit exceeded
|
|
func (p *AppPacket) Combine(other *AppPacket) bool {
|
|
// Create a new buffer with both packets
|
|
myData := p.SerializeApp()
|
|
otherData := other.SerializeApp()
|
|
|
|
// Check size limit (application packets can be larger than protocol)
|
|
if len(myData)+len(otherData) > 8192 { // Reasonable max size
|
|
return false
|
|
}
|
|
|
|
// Combine the data
|
|
newBuffer := make([]byte, len(myData)+len(otherData))
|
|
copy(newBuffer, myData)
|
|
copy(newBuffer[len(myData):], otherData)
|
|
|
|
// Update packet
|
|
p.Buffer = newBuffer
|
|
return true
|
|
}
|
|
|
|
// Copy creates a deep copy of the application packet
|
|
func (p *AppPacket) Copy() *AppPacket {
|
|
newPacket := &AppPacket{
|
|
EQPacket: *p.Clone(),
|
|
EmuOpcode: p.EmuOpcode,
|
|
OpcodeSize: p.OpcodeSize,
|
|
}
|
|
return newPacket
|
|
}
|
|
|
|
// SetVersion sets the protocol version for opcode conversion
|
|
func (p *AppPacket) SetVersion(version int16) {
|
|
p.Version = version
|
|
}
|
|
|
|
// ConvertToProtocolPacket wraps this application packet in a protocol packet
|
|
// This is used when sending application data over the network
|
|
func (p *AppPacket) ConvertToProtocolPacket() *ProtocolPacket {
|
|
// Serialize the application packet
|
|
appData := p.SerializeApp()
|
|
|
|
// Create protocol packet with OP_Packet opcode
|
|
proto := &ProtocolPacket{
|
|
EQPacket: *NewEQPacket(OP_Packet, appData),
|
|
}
|
|
|
|
// Copy network info
|
|
proto.SrcIP = p.SrcIP
|
|
proto.SrcPort = p.SrcPort
|
|
proto.DstIP = p.DstIP
|
|
proto.DstPort = p.DstPort
|
|
proto.Timestamp = p.Timestamp
|
|
proto.Version = p.Version
|
|
|
|
return proto
|
|
}
|
|
|
|
// AppCombine performs application-level packet combining (EQ2-specific)
|
|
// This is different from protocol-level combining
|
|
func AppCombine(packets []*AppPacket) *AppPacket {
|
|
if len(packets) == 0 {
|
|
return nil
|
|
}
|
|
if len(packets) == 1 {
|
|
return packets[0]
|
|
}
|
|
|
|
// Calculate total size needed
|
|
var totalSize int
|
|
for _, p := range packets {
|
|
data := p.SerializeApp()
|
|
if len(data) >= 255 {
|
|
totalSize += 3 + len(data) // 0xFF marker + 2-byte size + data
|
|
} else {
|
|
totalSize += 1 + len(data) // 1-byte size + data
|
|
}
|
|
}
|
|
|
|
// Build combined buffer
|
|
buffer := make([]byte, totalSize)
|
|
offset := 0
|
|
|
|
for _, p := range packets {
|
|
data := p.SerializeApp()
|
|
size := len(data)
|
|
|
|
if size >= 255 {
|
|
// Oversized packet: use 0xFF marker followed by 2-byte size
|
|
buffer[offset] = 0xFF
|
|
offset++
|
|
binary.BigEndian.PutUint16(buffer[offset:], uint16(size))
|
|
offset += 2
|
|
} else {
|
|
// Normal packet: 1-byte size
|
|
buffer[offset] = byte(size)
|
|
offset++
|
|
}
|
|
|
|
// Copy packet data
|
|
copy(buffer[offset:], data)
|
|
offset += size
|
|
}
|
|
|
|
// Create combined packet with OP_AppCombined
|
|
combined := &AppPacket{
|
|
EQPacket: *NewEQPacket(OP_AppCombined, buffer),
|
|
OpcodeSize: DefaultAppOpcodeSize,
|
|
}
|
|
|
|
return combined
|
|
}
|
|
|
|
// ExtractAppPackets extracts individual packets from an app-combined packet
|
|
func ExtractAppPackets(combined *AppPacket) ([]*AppPacket, error) {
|
|
if combined.Opcode != OP_AppCombined {
|
|
return nil, fmt.Errorf("not an app-combined packet")
|
|
}
|
|
|
|
var packets []*AppPacket
|
|
data := combined.Buffer
|
|
offset := 0
|
|
|
|
for offset < len(data) {
|
|
if offset >= len(data) {
|
|
break
|
|
}
|
|
|
|
var size int
|
|
// Read size
|
|
if data[offset] == 0xFF {
|
|
// Oversized packet
|
|
if offset+3 > len(data) {
|
|
return nil, fmt.Errorf("invalid oversized packet header")
|
|
}
|
|
offset++
|
|
size = int(binary.BigEndian.Uint16(data[offset:]))
|
|
offset += 2
|
|
} else {
|
|
// Normal packet
|
|
size = int(data[offset])
|
|
offset++
|
|
}
|
|
|
|
// Extract packet data
|
|
if offset+size > len(data) {
|
|
return nil, fmt.Errorf("packet size exceeds buffer")
|
|
}
|
|
|
|
packetData := data[offset : offset+size]
|
|
offset += size
|
|
|
|
// Parse the packet
|
|
app, err := ParseAppPacket(packetData, combined.OpcodeSize)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse app packet: %w", err)
|
|
}
|
|
|
|
packets = append(packets, app)
|
|
}
|
|
|
|
return packets, nil
|
|
}
|