eq2go/internal/packets/opcodes.go

320 lines
10 KiB
Go

package packets
import (
"fmt"
"sync"
)
// InternalOpcode represents the internal opcode enumeration
type InternalOpcode int32
// Internal opcode constants - these map to the C++ EmuOpcode enum
const (
OP_Unknown InternalOpcode = iota
// Login and authentication operations
OP_LoginReplyMsg
OP_LoginByNumRequestMsg
OP_WSLoginRequestMsg
// Server initialization and zone management
OP_ESInitMsg
OP_ESReadyForClientsMsg
OP_CreateZoneInstanceMsg
OP_ZoneInstanceCreateReplyMsg
OP_ZoneInstanceDestroyedMsg
OP_ExpectClientAsCharacterRequest
OP_ExpectClientAsCharacterReplyMs
OP_ZoneInfoMsg
// Character creation and loading
OP_CreateCharacterRequestMsg
OP_DoneLoadingZoneResourcesMsg
OP_DoneSendingInitialEntitiesMsg
OP_DoneLoadingEntityResourcesMsg
OP_DoneLoadingUIResourcesMsg
// Game state updates
OP_PredictionUpdateMsg
OP_RemoteCmdMsg
OP_SetRemoteCmdsMsg
OP_GameWorldTimeMsg
OP_MOTDMsg
OP_ZoneMOTDMsg
// Command dispatching
OP_ClientCmdMsg
OP_DispatchClientCmdMsg
OP_DispatchESMsg
// Character sheet and inventory updates
OP_UpdateCharacterSheetMsg
OP_UpdateSpellBookMsg
OP_UpdateInventoryMsg
// Zone transitions
OP_ChangeZoneMsg
OP_ClientTeleportRequestMsg
OP_TeleportWithinZoneMsg
OP_ReadyToZoneMsg
// Chat system
OP_ChatTellChannelMsg
OP_ChatTellUserMsg
// Position updates
OP_UpdatePositionMsg
// Achievement system
OP_AchievementUpdateMsg
OP_CharacterAchievements
// Title system
OP_TitleUpdateMsg
OP_CharacterTitles
OP_SetActiveTitleMsg
// NPC system
OP_NPCAttackMsg
OP_NPCTargetMsg
OP_NPCInfoMsg
OP_NPCSpellCastMsg
OP_NPCMovementMsg
// Item system
OP_ItemMoveMsg
OP_ItemEquipMsg
OP_ItemUnequipMsg
OP_ItemPickupMsg
OP_ItemDropMsg
OP_ItemExamineMsg
OP_ItemUpdateMsg
// EverQuest specific commands - Core
OP_EqHearChatCmd
OP_EqDisplayTextCmd
OP_EqCreateGhostCmd
OP_EqCreateWidgetCmd
OP_EqDestroyGhostCmd
OP_EqUpdateGhostCmd
OP_EqSetControlGhostCmd
OP_EqSetPOVGhostCmd
// Add more opcodes as needed...
_maxInternalOpcode // Sentinel value
)
// OpcodeNames maps internal opcodes to their string names for debugging
var OpcodeNames = map[InternalOpcode]string{
OP_Unknown: "OP_Unknown",
OP_LoginReplyMsg: "OP_LoginReplyMsg",
OP_LoginByNumRequestMsg: "OP_LoginByNumRequestMsg",
OP_WSLoginRequestMsg: "OP_WSLoginRequestMsg",
OP_ESInitMsg: "OP_ESInitMsg",
OP_ESReadyForClientsMsg: "OP_ESReadyForClientsMsg",
OP_CreateZoneInstanceMsg: "OP_CreateZoneInstanceMsg",
OP_ZoneInstanceCreateReplyMsg: "OP_ZoneInstanceCreateReplyMsg",
OP_ZoneInstanceDestroyedMsg: "OP_ZoneInstanceDestroyedMsg",
OP_ExpectClientAsCharacterRequest: "OP_ExpectClientAsCharacterRequest",
OP_ExpectClientAsCharacterReplyMs: "OP_ExpectClientAsCharacterReplyMs",
OP_ZoneInfoMsg: "OP_ZoneInfoMsg",
OP_CreateCharacterRequestMsg: "OP_CreateCharacterRequestMsg",
OP_DoneLoadingZoneResourcesMsg: "OP_DoneLoadingZoneResourcesMsg",
OP_DoneSendingInitialEntitiesMsg: "OP_DoneSendingInitialEntitiesMsg",
OP_DoneLoadingEntityResourcesMsg: "OP_DoneLoadingEntityResourcesMsg",
OP_DoneLoadingUIResourcesMsg: "OP_DoneLoadingUIResourcesMsg",
OP_PredictionUpdateMsg: "OP_PredictionUpdateMsg",
OP_RemoteCmdMsg: "OP_RemoteCmdMsg",
OP_SetRemoteCmdsMsg: "OP_SetRemoteCmdsMsg",
OP_GameWorldTimeMsg: "OP_GameWorldTimeMsg",
OP_MOTDMsg: "OP_MOTDMsg",
OP_ZoneMOTDMsg: "OP_ZoneMOTDMsg",
OP_ClientCmdMsg: "OP_ClientCmdMsg",
OP_DispatchClientCmdMsg: "OP_DispatchClientCmdMsg",
OP_DispatchESMsg: "OP_DispatchESMsg",
OP_UpdateCharacterSheetMsg: "OP_UpdateCharacterSheetMsg",
OP_UpdateSpellBookMsg: "OP_UpdateSpellBookMsg",
OP_UpdateInventoryMsg: "OP_UpdateInventoryMsg",
OP_ChangeZoneMsg: "OP_ChangeZoneMsg",
OP_ClientTeleportRequestMsg: "OP_ClientTeleportRequestMsg",
OP_TeleportWithinZoneMsg: "OP_TeleportWithinZoneMsg",
OP_ReadyToZoneMsg: "OP_ReadyToZoneMsg",
OP_ChatTellChannelMsg: "OP_ChatTellChannelMsg",
OP_ChatTellUserMsg: "OP_ChatTellUserMsg",
OP_UpdatePositionMsg: "OP_UpdatePositionMsg",
OP_AchievementUpdateMsg: "OP_AchievementUpdateMsg",
OP_CharacterAchievements: "OP_CharacterAchievements",
OP_TitleUpdateMsg: "OP_TitleUpdateMsg",
OP_CharacterTitles: "OP_CharacterTitles",
OP_SetActiveTitleMsg: "OP_SetActiveTitleMsg",
OP_NPCAttackMsg: "OP_NPCAttackMsg",
OP_NPCTargetMsg: "OP_NPCTargetMsg",
OP_NPCInfoMsg: "OP_NPCInfoMsg",
OP_NPCSpellCastMsg: "OP_NPCSpellCastMsg",
OP_NPCMovementMsg: "OP_NPCMovementMsg",
OP_ItemMoveMsg: "OP_ItemMoveMsg",
OP_ItemEquipMsg: "OP_ItemEquipMsg",
OP_ItemUnequipMsg: "OP_ItemUnequipMsg",
OP_ItemPickupMsg: "OP_ItemPickupMsg",
OP_ItemDropMsg: "OP_ItemDropMsg",
OP_ItemExamineMsg: "OP_ItemExamineMsg",
OP_ItemUpdateMsg: "OP_ItemUpdateMsg",
OP_EqHearChatCmd: "OP_EqHearChatCmd",
OP_EqDisplayTextCmd: "OP_EqDisplayTextCmd",
OP_EqCreateGhostCmd: "OP_EqCreateGhostCmd",
OP_EqCreateWidgetCmd: "OP_EqCreateWidgetCmd",
OP_EqDestroyGhostCmd: "OP_EqDestroyGhostCmd",
OP_EqUpdateGhostCmd: "OP_EqUpdateGhostCmd",
OP_EqSetControlGhostCmd: "OP_EqSetControlGhostCmd",
OP_EqSetPOVGhostCmd: "OP_EqSetPOVGhostCmd",
}
// OpcodeManager handles the mapping between client-specific opcodes and internal opcodes
type OpcodeManager struct {
// Maps client version -> (client opcode -> internal opcode)
clientToInternal map[int32]map[uint16]InternalOpcode
// Maps internal opcode -> client version -> client opcode
internalToClient map[InternalOpcode]map[int32]uint16
mutex sync.RWMutex
}
// NewOpcodeManager creates a new opcode manager
func NewOpcodeManager() *OpcodeManager {
return &OpcodeManager{
clientToInternal: make(map[int32]map[uint16]InternalOpcode),
internalToClient: make(map[InternalOpcode]map[int32]uint16),
}
}
// LoadOpcodeMap loads opcode mappings for a specific client version
func (om *OpcodeManager) LoadOpcodeMap(clientVersion int32, opcodeMap map[string]uint16) error {
om.mutex.Lock()
defer om.mutex.Unlock()
// Initialize maps for this client version
if om.clientToInternal[clientVersion] == nil {
om.clientToInternal[clientVersion] = make(map[uint16]InternalOpcode)
}
// Process each opcode mapping
for opcodeName, clientOpcode := range opcodeMap {
// Find the internal opcode for this name
internalOpcode := OP_Unknown
for intOp, name := range OpcodeNames {
if name == opcodeName {
internalOpcode = intOp
break
}
}
if internalOpcode == OP_Unknown && opcodeName != "OP_Unknown" {
// Log warning for unknown opcode but don't fail
fmt.Printf("Warning: Unknown internal opcode name: %s\n", opcodeName)
continue
}
// Set client -> internal mapping
om.clientToInternal[clientVersion][clientOpcode] = internalOpcode
// Set internal -> client mapping
if om.internalToClient[internalOpcode] == nil {
om.internalToClient[internalOpcode] = make(map[int32]uint16)
}
om.internalToClient[internalOpcode][clientVersion] = clientOpcode
}
fmt.Printf("Loaded %d opcode mappings for client version %d\n", len(opcodeMap), clientVersion)
return nil
}
// ClientOpcodeToInternal converts a client opcode to internal opcode
func (om *OpcodeManager) ClientOpcodeToInternal(clientVersion int32, clientOpcode uint16) InternalOpcode {
om.mutex.RLock()
defer om.mutex.RUnlock()
if versionMap, exists := om.clientToInternal[clientVersion]; exists {
if internalOp, found := versionMap[clientOpcode]; found {
return internalOp
}
}
return OP_Unknown
}
// InternalOpcodeToClient converts an internal opcode to client opcode
func (om *OpcodeManager) InternalOpcodeToClient(internalOpcode InternalOpcode, clientVersion int32) uint16 {
om.mutex.RLock()
defer om.mutex.RUnlock()
if versionMap, exists := om.internalToClient[internalOpcode]; exists {
if clientOp, found := versionMap[clientVersion]; found {
return clientOp
}
}
return 0 // Invalid client opcode
}
// GetOpcodeName returns the human-readable name for an internal opcode
func (om *OpcodeManager) GetOpcodeName(internalOpcode InternalOpcode) string {
if name, exists := OpcodeNames[internalOpcode]; exists {
return name
}
return "OP_Unknown"
}
// GetSupportedVersions returns all client versions with loaded opcodes
func (om *OpcodeManager) GetSupportedVersions() []int32 {
om.mutex.RLock()
defer om.mutex.RUnlock()
versions := make([]int32, 0, len(om.clientToInternal))
for version := range om.clientToInternal {
versions = append(versions, version)
}
return versions
}
// GetOpcodeCount returns the number of opcodes loaded for a client version
func (om *OpcodeManager) GetOpcodeCount(clientVersion int32) int {
om.mutex.RLock()
defer om.mutex.RUnlock()
if versionMap, exists := om.clientToInternal[clientVersion]; exists {
return len(versionMap)
}
return 0
}
// Global opcode manager instance
var globalOpcodeManager = NewOpcodeManager()
// GetOpcodeManager returns the global opcode manager
func GetOpcodeManager() *OpcodeManager {
return globalOpcodeManager
}
// Convenience functions for global access
// LoadGlobalOpcodeMap loads opcodes into the global manager
func LoadGlobalOpcodeMap(clientVersion int32, opcodeMap map[string]uint16) error {
return globalOpcodeManager.LoadOpcodeMap(clientVersion, opcodeMap)
}
// ClientToInternal converts using the global manager
func ClientToInternal(clientVersion int32, clientOpcode uint16) InternalOpcode {
return globalOpcodeManager.ClientOpcodeToInternal(clientVersion, clientOpcode)
}
// InternalToClient converts using the global manager
func InternalToClient(internalOpcode InternalOpcode, clientVersion int32) uint16 {
return globalOpcodeManager.InternalOpcodeToClient(internalOpcode, clientVersion)
}
// GetInternalOpcodeName returns name using the global manager
func GetInternalOpcodeName(internalOpcode InternalOpcode) string {
return globalOpcodeManager.GetOpcodeName(internalOpcode)
}