package eq2net import ( "encoding/binary" "fmt" ) // GameEmuOpcode represents emulator opcodes for the game server type GameEmuOpcode uint16 // Game server emulator opcodes (partial list - there are hundreds) const ( GOP_Unknown GameEmuOpcode = iota GOP_LoginReplyMsg GOP_LoginByNumRequestMsg GOP_WSLoginRequestMsg GOP_ESInitMsg GOP_ESReadyForClientsMsg GOP_CreateZoneInstanceMsg GOP_ZoneInstanceCreateReplyMsg GOP_ZoneInstanceDestroyedMsg GOP_ExpectClientAsCharacterRequest GOP_ExpectClientAsCharacterReplyMs GOP_ZoneInfoMsg GOP_CreateCharacterRequestMsg GOP_DoneLoadingZoneResourcesMsg GOP_DoneSendingInitialEntitiesMsg GOP_DoneLoadingEntityResourcesMsg GOP_DoneLoadingUIResourcesMsg GOP_PredictionUpdateMsg GOP_RemoteCmdMsg GOP_SetRemoteCmdsMsg GOP_GameWorldTimeMsg GOP_MOTDMsg GOP_ZoneMOTDMsg GOP_GuildRecruitingMemberInfo GOP_GuildRecruiting GOP_GuildRecruitingDetails GOP_GuildRecruitingImage GOP_AvatarCreatedMsg GOP_AvatarDestroyedMsg GOP_RequestCampMsg GOP_MapRequest GOP_CampStartedMsg GOP_CampAbortedMsg GOP_WhoQueryRequestMsg GOP_WhoQueryReplyMsg GOP_RemoveClientFromGroupMsg GOP_GroupCreatedMsg GOP_GroupDestroyedMsg GOP_GroupMemberAddedMsg GOP_GroupMemberRemovedMsg GOP_GroupInfoRequestMsg GOP_GroupInfoUpdateMsg GOP_GroupRemovedFromGroupMsg GOP_GroupLeaderChangedMsg GOP_GroupOptionsMsg GOP_UpdateDataMsg GOP_UpdateSpawnMsg GOP_UpdateCharacterSheetMsg GOP_UpdateSkillsMsg GOP_UpdateQuestMsg GOP_UpdateInventoryMsg GOP_UpdatePositionMsg GOP_UpdateRaidMsg GOP_UpdateTradeMsg GOP_UpdateTargetMsg GOP_UpdateTargetLocMsg GOP_UpdateActorTargetMsg GOP_UpdatePlayerMailMsg GOP_UpdatePlayerMail GOP_UpdateTimeMsg GOP_MaxGameOpcode // Add more as needed ) // GamePacket represents a game server packet type GamePacket struct { EQPacket // Embed base packet GameEmuOpcode GameEmuOpcode // Game server emulator opcode } // NewGamePacket creates a new game server packet func NewGamePacket(opcode GameEmuOpcode, data []byte) *GamePacket { base := NewEQPacket(uint16(opcode), data) base.EmuOpcode = uint16(opcode) base.OpcodeSize = 2 // Game server uses 2-byte opcodes p := &GamePacket{ EQPacket: *base, GameEmuOpcode: opcode, } return p } // ParseGamePacket creates a game packet from raw data func ParseGamePacket(data []byte) (*GamePacket, error) { if len(data) < 2 { return nil, fmt.Errorf("packet too small for game opcode") } // Extract 2-byte opcode var opcode GameEmuOpcode var remainingData []byte // Handle special encoding for 2-byte opcodes // Special case: When we have 3+ bytes and first is 0x00 and third is 0x00, // it's the special encoding for opcodes with low byte = 0x00 if len(data) > 2 && data[0] == 0x00 && data[2] == 0x00 { // Extra 0x00 prefix for opcodes like 0x1200 opcode = GameEmuOpcode(binary.BigEndian.Uint16(data[1:3])) remainingData = data[3:] // Skip the extra byte } else { opcode = GameEmuOpcode(binary.BigEndian.Uint16(data[0:2])) remainingData = data[2:] } // Create packet with remaining data base := NewEQPacket(uint16(opcode), remainingData) base.EmuOpcode = uint16(opcode) base.OpcodeSize = 2 p := &GamePacket{ EQPacket: *base, GameEmuOpcode: opcode, } return p, nil } // GetGameOpcode returns the game emulator opcode func (p *GamePacket) GetGameOpcode() GameEmuOpcode { return p.GameEmuOpcode } // SetGameOpcode sets the game emulator opcode func (p *GamePacket) SetGameOpcode(opcode GameEmuOpcode) { p.GameEmuOpcode = opcode p.EmuOpcode = uint16(opcode) p.Opcode = uint16(opcode) } // SerializeGame serializes the game packet with proper encoding func (p *GamePacket) SerializeGame() []byte { // Handle special encoding rules for 2-byte opcodes opcodeBytes := 2 extraBytes := 0 if (uint16(p.GameEmuOpcode) & 0x00FF) == 0 { // Opcodes with low byte = 0x00 need an extra 0x00 prefix extraBytes = 1 } // Create buffer buf := make([]byte, opcodeBytes+extraBytes+len(p.Buffer)) offset := 0 // Write opcode if extraBytes > 0 { // Special encoding: add 0x00 prefix buf[offset] = 0x00 offset++ binary.BigEndian.PutUint16(buf[offset:], uint16(p.GameEmuOpcode)) offset += 2 } else { binary.BigEndian.PutUint16(buf[offset:], uint16(p.GameEmuOpcode)) offset += 2 } // Copy packet data if len(p.Buffer) > 0 { copy(buf[offset:], p.Buffer) } return buf } // GetGameOpcodeName returns the string name of a game emulator opcode func GetGameOpcodeName(opcode GameEmuOpcode) string { switch opcode { case GOP_Unknown: return "GOP_Unknown" case GOP_LoginReplyMsg: return "GOP_LoginReplyMsg" case GOP_LoginByNumRequestMsg: return "GOP_LoginByNumRequestMsg" case GOP_WSLoginRequestMsg: return "GOP_WSLoginRequestMsg" case GOP_ESInitMsg: return "GOP_ESInitMsg" case GOP_ESReadyForClientsMsg: return "GOP_ESReadyForClientsMsg" case GOP_CreateZoneInstanceMsg: return "GOP_CreateZoneInstanceMsg" case GOP_ZoneInstanceCreateReplyMsg: return "GOP_ZoneInstanceCreateReplyMsg" case GOP_ZoneInstanceDestroyedMsg: return "GOP_ZoneInstanceDestroyedMsg" case GOP_ExpectClientAsCharacterRequest: return "GOP_ExpectClientAsCharacterRequest" case GOP_ExpectClientAsCharacterReplyMs: return "GOP_ExpectClientAsCharacterReplyMs" case GOP_ZoneInfoMsg: return "GOP_ZoneInfoMsg" case GOP_CreateCharacterRequestMsg: return "GOP_CreateCharacterRequestMsg" case GOP_DoneLoadingZoneResourcesMsg: return "GOP_DoneLoadingZoneResourcesMsg" case GOP_DoneSendingInitialEntitiesMsg: return "GOP_DoneSendingInitialEntitiesMsg" case GOP_DoneLoadingEntityResourcesMsg: return "GOP_DoneLoadingEntityResourcesMsg" case GOP_DoneLoadingUIResourcesMsg: return "GOP_DoneLoadingUIResourcesMsg" case GOP_PredictionUpdateMsg: return "GOP_PredictionUpdateMsg" case GOP_RemoteCmdMsg: return "GOP_RemoteCmdMsg" case GOP_SetRemoteCmdsMsg: return "GOP_SetRemoteCmdsMsg" case GOP_GameWorldTimeMsg: return "GOP_GameWorldTimeMsg" case GOP_MOTDMsg: return "GOP_MOTDMsg" case GOP_ZoneMOTDMsg: return "GOP_ZoneMOTDMsg" case GOP_GuildRecruitingMemberInfo: return "GOP_GuildRecruitingMemberInfo" case GOP_GuildRecruiting: return "GOP_GuildRecruiting" case GOP_GuildRecruitingDetails: return "GOP_GuildRecruitingDetails" case GOP_GuildRecruitingImage: return "GOP_GuildRecruitingImage" case GOP_AvatarCreatedMsg: return "GOP_AvatarCreatedMsg" case GOP_AvatarDestroyedMsg: return "GOP_AvatarDestroyedMsg" case GOP_RequestCampMsg: return "GOP_RequestCampMsg" case GOP_MapRequest: return "GOP_MapRequest" case GOP_CampStartedMsg: return "GOP_CampStartedMsg" case GOP_CampAbortedMsg: return "GOP_CampAbortedMsg" case GOP_WhoQueryRequestMsg: return "GOP_WhoQueryRequestMsg" case GOP_WhoQueryReplyMsg: return "GOP_WhoQueryReplyMsg" case GOP_RemoveClientFromGroupMsg: return "GOP_RemoveClientFromGroupMsg" case GOP_GroupCreatedMsg: return "GOP_GroupCreatedMsg" case GOP_GroupDestroyedMsg: return "GOP_GroupDestroyedMsg" case GOP_GroupMemberAddedMsg: return "GOP_GroupMemberAddedMsg" case GOP_GroupMemberRemovedMsg: return "GOP_GroupMemberRemovedMsg" case GOP_GroupInfoRequestMsg: return "GOP_GroupInfoRequestMsg" case GOP_GroupInfoUpdateMsg: return "GOP_GroupInfoUpdateMsg" case GOP_GroupRemovedFromGroupMsg: return "GOP_GroupRemovedFromGroupMsg" case GOP_GroupLeaderChangedMsg: return "GOP_GroupLeaderChangedMsg" case GOP_GroupOptionsMsg: return "GOP_GroupOptionsMsg" case GOP_UpdateDataMsg: return "GOP_UpdateDataMsg" case GOP_UpdateSpawnMsg: return "GOP_UpdateSpawnMsg" case GOP_UpdateCharacterSheetMsg: return "GOP_UpdateCharacterSheetMsg" case GOP_UpdateSkillsMsg: return "GOP_UpdateSkillsMsg" case GOP_UpdateQuestMsg: return "GOP_UpdateQuestMsg" case GOP_UpdateInventoryMsg: return "GOP_UpdateInventoryMsg" case GOP_UpdatePositionMsg: return "GOP_UpdatePositionMsg" case GOP_UpdateRaidMsg: return "GOP_UpdateRaidMsg" case GOP_UpdateTradeMsg: return "GOP_UpdateTradeMsg" case GOP_UpdateTargetMsg: return "GOP_UpdateTargetMsg" case GOP_UpdateTargetLocMsg: return "GOP_UpdateTargetLocMsg" case GOP_UpdateActorTargetMsg: return "GOP_UpdateActorTargetMsg" case GOP_UpdatePlayerMailMsg: return "GOP_UpdatePlayerMailMsg" case GOP_UpdatePlayerMail: return "GOP_UpdatePlayerMail" case GOP_UpdateTimeMsg: return "GOP_UpdateTimeMsg" default: return fmt.Sprintf("Unknown(%d)", opcode) } } // ConvertToProtocolPacket wraps this game packet in a protocol packet func (p *GamePacket) ConvertToProtocolPacket() *ProtocolPacket { // Serialize the game packet gameData := p.SerializeGame() // Create protocol packet with OP_Packet opcode proto := NewProtocolPacket(OP_Packet, gameData) // 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 } // Copy creates a deep copy of the game packet func (p *GamePacket) Copy() *GamePacket { newPacket := &GamePacket{ EQPacket: *p.Clone(), GameEmuOpcode: p.GameEmuOpcode, } return newPacket } // CombineGamePackets combines multiple game packets for efficient transmission func CombineGamePackets(packets []*GamePacket) *ProtocolPacket { if len(packets) == 0 { return nil } if len(packets) == 1 { return packets[0].ConvertToProtocolPacket() } // Build combined buffer var buf []byte for _, p := range packets { data := p.SerializeGame() size := len(data) // Add size encoding if size >= 255 { // Oversized packet buf = append(buf, 0xFF) buf = append(buf, byte(size>>8), byte(size)) } else { buf = append(buf, byte(size)) } // Add packet data buf = append(buf, data...) } // Create combined protocol packet return NewProtocolPacket(OP_AppCombined, buf) }