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 }