package packets import ( "encoding/binary" "git.sharkk.net/EQ2/Protocol/opcodes" ) // DefaultOpcodeSize is the default opcode size for application packets var DefaultOpcodeSize uint8 = 2 // AppPacket handles high-level game opcodes (matches EQApplicationPacket) type AppPacket struct { *Packet emuOpcode opcodes.EmuOpcode // Cached emulator opcode opcodeSize uint8 // Size of opcode in bytes (1 or 2) manager opcodes.Manager // Opcode manager for translation } // NewAppPacket creates a new application packet func NewAppPacket(manager opcodes.Manager) *AppPacket { return &AppPacket{ Packet: NewPacket(0, nil), emuOpcode: opcodes.OP_Unknown, opcodeSize: DefaultOpcodeSize, manager: manager, } } // NewAppPacketWithOp creates with opcode func NewAppPacketWithOp(op opcodes.EmuOpcode, manager opcodes.Manager) *AppPacket { app := &AppPacket{ Packet: NewPacket(0, nil), opcodeSize: DefaultOpcodeSize, manager: manager, } app.SetOpcode(op) return app } // NewAppPacketWithSize creates with opcode and size func NewAppPacketWithSize(op opcodes.EmuOpcode, size uint32, manager opcodes.Manager) *AppPacket { app := &AppPacket{ Packet: NewPacket(0, make([]byte, size)), opcodeSize: DefaultOpcodeSize, manager: manager, } app.SetOpcode(op) return app } // NewAppPacketWithData creates with opcode and data func NewAppPacketWithData(op opcodes.EmuOpcode, data []byte, manager opcodes.Manager) *AppPacket { app := &AppPacket{ Packet: NewPacket(0, data), opcodeSize: DefaultOpcodeSize, manager: manager, } app.SetOpcode(op) return app } // NewAppPacketFromRaw creates from raw buffer (matches EQApplicationPacket constructor) func NewAppPacketFromRaw(buf []byte, opcodeSize uint8, manager opcodes.Manager) *AppPacket { if opcodeSize == 0 { opcodeSize = DefaultOpcodeSize } app := &AppPacket{ Packet: NewPacket(0, nil), opcodeSize: opcodeSize, manager: manager, } offset := 0 // Extract opcode based on size if opcodeSize == 1 && len(buf) >= 1 { app.Opcode = uint16(buf[0]) offset = 1 } else if len(buf) >= 2 { app.Opcode = binary.BigEndian.Uint16(buf[:2]) offset = 2 } // Copy remaining data as payload if len(buf) > offset { app.Buffer = make([]byte, len(buf)-offset) copy(app.Buffer, buf[offset:]) } // Convert EQ opcode to emulator opcode if app.manager != nil { app.emuOpcode = app.manager.EQToEmu(app.Opcode) } else { app.emuOpcode = opcodes.EmuOpcode(app.Opcode) } return app } // Size returns total packet size including opcode func (a *AppPacket) Size() uint32 { // Handle special encoding where low byte = 0x00 needs extra byte extraBytes := uint32(0) if a.opcodeSize == 2 && (a.Opcode&0x00ff) == 0 { extraBytes = 1 } return uint32(len(a.Buffer)) + uint32(a.opcodeSize) + extraBytes } // SetOpcode sets the emulator opcode and translates to EQ opcode func (a *AppPacket) SetOpcode(op opcodes.EmuOpcode) { a.emuOpcode = op if a.manager != nil { a.Opcode = a.manager.EmuToEQ(op) } else { a.Opcode = uint16(op) } } // GetOpcode returns the emulator opcode func (a *AppPacket) GetOpcode() opcodes.EmuOpcode { if a.emuOpcode == opcodes.OP_Unknown && a.manager != nil { a.emuOpcode = a.manager.EQToEmu(a.Opcode) } 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" } // SetManager sets the opcode manager 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) } } // Serialize writes to destination (matches EQApplicationPacket::serialize) func (a *AppPacket) Serialize(dest []byte) uint32 { opcodeBytes := a.opcodeSize pos := 0 if a.opcodeSize == 1 { // Single-byte opcode dest[pos] = byte(a.Opcode) pos++ } else { // Two-byte opcode with special encoding rules // Application opcodes with low byte = 0x00 need extra 0x00 prefix if (a.Opcode & 0x00ff) == 0 { dest[pos] = 0 pos++ binary.BigEndian.PutUint16(dest[pos:], a.Opcode) pos += 2 opcodeBytes++ } else { binary.BigEndian.PutUint16(dest[pos:], a.Opcode) pos += 2 } } // Copy packet data after opcode copy(dest[pos:], a.Buffer) return uint32(len(a.Buffer)) + uint32(opcodeBytes) } // Combine combines with another app packet (matches EQApplicationPacket::combine) func (a *AppPacket) Combine(rhs *AppPacket) bool { // Application packet combining is not implemented in original // Use protocol-level combining instead return false } // Copy creates a deep copy func (a *AppPacket) Copy() *AppPacket { newApp := &AppPacket{ Packet: NewPacket(a.Opcode, a.Buffer), emuOpcode: a.emuOpcode, opcodeSize: a.opcodeSize, manager: a.manager, } newApp.Packet.CopyInfo(a.Packet) return newApp } // ToProto converts to protocol packet func (a *AppPacket) ToProto() *ProtoPacket { // Serialize the application packet tmpBuf := make([]byte, a.Size()) a.Serialize(tmpBuf) proto := NewProtoPacket(opcodes.OP_Packet, tmpBuf, a.manager) proto.LoginOp = a.emuOpcode proto.CopyInfo(a.Packet) return proto }