1
0
Protocol/app_packet.go
2025-09-02 12:30:36 -05:00

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
}