1
0
Protocol/game_packet.go
2025-09-02 20:25:42 -05:00

365 lines
9.6 KiB
Go

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)
}