package packets import ( "encoding/binary" ) // DefaultOpcodeSize is the default opcode size for application packets 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 // This is the main packet type used by game logic type AppPacket struct { *Packet emuOpcode EmuOpcode // Cached emulator opcode opcodeSize uint8 // Size of opcode in bytes (1 or 2) } // NewAppPacket creates a new application packet func NewAppPacket() *AppPacket { return &AppPacket{ Packet: NewPacket(0, nil), emuOpcode: OpUnknown, opcodeSize: DefaultOpcodeSize, } } // NewAppPacketWithOp creates a new application packet with opcode func NewAppPacketWithOp(op EmuOpcode) *AppPacket { app := &AppPacket{ Packet: NewPacket(0, nil), opcodeSize: DefaultOpcodeSize, } app.SetOpcode(op) return app } // NewAppPacketWithSize creates a new application packet with opcode and size func NewAppPacketWithSize(op EmuOpcode, size uint32) *AppPacket { app := &AppPacket{ Packet: NewPacket(0, make([]byte, size)), opcodeSize: DefaultOpcodeSize, } app.SetOpcode(op) return app } // NewAppPacketWithData creates a new application packet with opcode and data func NewAppPacketWithData(op EmuOpcode, data []byte) *AppPacket { app := &AppPacket{ Packet: NewPacket(0, data), opcodeSize: DefaultOpcodeSize, } app.SetOpcode(op) return app } // NewAppPacketFromRaw creates app packet from raw buffer (used by ProtoPacket) // Assumes first bytes are opcode based on opcodeSize func NewAppPacketFromRaw(buf []byte, opcodeSize uint8) *AppPacket { if opcodeSize == 0 { opcodeSize = DefaultOpcodeSize } app := &AppPacket{ Packet: NewPacket(0, nil), opcodeSize: opcodeSize, } if opcodeSize == 1 && len(buf) >= 1 { app.Opcode = uint16(buf[0]) if len(buf) > 1 { app.Buffer = make([]byte, len(buf)-1) copy(app.Buffer, buf[1:]) } } else if len(buf) >= 2 { app.Opcode = binary.BigEndian.Uint16(buf[:2]) if len(buf) > 2 { app.Buffer = make([]byte, len(buf)-2) copy(app.Buffer, buf[2:]) } } return app } // Size returns total packet size including opcode func (a *AppPacket) Size() uint32 { return uint32(len(a.Buffer)) + uint32(a.opcodeSize) } // SetOpcodeSize sets the opcode size func (a *AppPacket) SetOpcodeSize(size uint8) { a.opcodeSize = size } // SetOpcode sets the emulator opcode func (a *AppPacket) SetOpcode(op EmuOpcode) { a.emuOpcode = op // In full implementation, this would convert to protocol opcode // using opcode manager. For now, direct assignment a.Opcode = uint16(op) } // GetOpcode returns the emulator opcode (with caching) func (a *AppPacket) GetOpcode() EmuOpcode { if a.emuOpcode == OpUnknown { // In full implementation, convert from protocol opcode a.emuOpcode = EmuOpcode(a.Opcode) } return a.emuOpcode } // Copy creates a deep copy of this application packet func (a *AppPacket) Copy() *AppPacket { newApp := &AppPacket{ Packet: NewPacket(a.Opcode, a.Buffer), emuOpcode: a.emuOpcode, opcodeSize: a.opcodeSize, } newApp.Packet.CopyInfo(a.Packet) return newApp } // Serialize writes the application packet to a destination buffer // Handles special opcode encoding rules for application-level packets 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 this packet with another application packet func (a *AppPacket) Combine(rhs *AppPacket) bool { const opAppCombined = 0x19 // OP_AppCombined // Check if we can combine these packets totalSize := a.Size() + rhs.Size() if totalSize > 512 { // Reasonable max combined packet size return false } // If this isn't already a combined packet, convert it if a.Opcode != opAppCombined { // Create combined packet structure oldSize := a.Size() newSize := oldSize + rhs.Size() + 2 // +2 for size headers newBuffer := make([]byte, newSize) pos := 0 // Write first packet size and data newBuffer[pos] = byte(oldSize) pos++ // Serialize first packet tmpBuf := make([]byte, oldSize) a.Serialize(tmpBuf) copy(newBuffer[pos:], tmpBuf) pos += int(oldSize) // Write second packet size and data rhsSize := rhs.Size() newBuffer[pos] = byte(rhsSize) pos++ // Serialize second packet tmpBuf = make([]byte, rhsSize) rhs.Serialize(tmpBuf) copy(newBuffer[pos:], tmpBuf) // Update this packet to be a combined packet a.Opcode = opAppCombined a.Buffer = newBuffer return true } // This is already a combined packet, add to it rhsSize := rhs.Size() if rhsSize >= 255 { // Oversized packet handling newBuffer := make([]byte, len(a.Buffer)+int(rhsSize)+3) copy(newBuffer, a.Buffer) pos := len(a.Buffer) newBuffer[pos] = 255 // Oversized marker pos++ binary.BigEndian.PutUint16(newBuffer[pos:], uint16(rhsSize)) pos += 2 tmpBuf := make([]byte, rhsSize) rhs.Serialize(tmpBuf) copy(newBuffer[pos:], tmpBuf) a.Buffer = newBuffer } else { // Normal sized addition newBuffer := make([]byte, len(a.Buffer)+int(rhsSize)+1) copy(newBuffer, a.Buffer) pos := len(a.Buffer) newBuffer[pos] = byte(rhsSize) pos++ tmpBuf := make([]byte, rhsSize) rhs.Serialize(tmpBuf) copy(newBuffer[pos:], tmpBuf) a.Buffer = newBuffer } return true }