292 lines
9.4 KiB
Go
292 lines
9.4 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
|
|
|
|
// 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_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)
|
|
} |