1
0
Protocol/structs/opcode_manager.go

239 lines
7.3 KiB
Go

package packets
import (
"fmt"
"sync"
"database/sql"
"log"
)
// OpcodeManager manages opcode mappings for different client versions
type OpcodeManager struct {
versions map[int32]int32 // Maps version range start to end
opcodes map[int32]map[string]uint16 // Maps version to opcode name->value
internalOpcodes map[int32]map[uint16]InternalOpcode // Maps version to wire opcode->internal
mu sync.RWMutex
}
// NewOpcodeManager creates a new opcode manager
func NewOpcodeManager() *OpcodeManager {
return &OpcodeManager{
versions: make(map[int32]int32),
opcodes: make(map[int32]map[string]uint16),
internalOpcodes: make(map[int32]map[uint16]InternalOpcode),
}
}
// LoadVersionsFromDB loads version ranges from the database
func (om *OpcodeManager) LoadVersionsFromDB(db *sql.DB) error {
om.mu.Lock()
defer om.mu.Unlock()
query := `SELECT DISTINCT version_range1, version_range2 FROM opcodes`
rows, err := db.Query(query)
if err != nil {
return fmt.Errorf("failed to query versions: %w", err)
}
defer rows.Close()
for rows.Next() {
var start, end int32
if err := rows.Scan(&start, &end); err != nil {
return fmt.Errorf("failed to scan version row: %w", err)
}
om.versions[start] = end
}
return rows.Err()
}
// LoadOpcodesFromDB loads opcodes for all versions from the database
func (om *OpcodeManager) LoadOpcodesFromDB(db *sql.DB) error {
om.mu.Lock()
defer om.mu.Unlock()
for versionStart := range om.versions {
query := `SELECT name, opcode FROM opcodes
WHERE ? BETWEEN version_range1 AND version_range2
ORDER BY version_range1, id`
rows, err := db.Query(query, versionStart)
if err != nil {
return fmt.Errorf("failed to query opcodes for version %d: %w", versionStart, err)
}
opcodes := make(map[string]uint16)
internal := make(map[uint16]InternalOpcode)
for rows.Next() {
var name string
var opcode uint16
if err := rows.Scan(&name, &opcode); err != nil {
rows.Close()
return fmt.Errorf("failed to scan opcode row: %w", err)
}
opcodes[name] = opcode
// Map to internal opcodes
if internalOp, ok := nameToInternalOpcode[name]; ok {
internal[opcode] = internalOp
}
}
rows.Close()
om.opcodes[versionStart] = opcodes
om.internalOpcodes[versionStart] = internal
// Silent - we'll log summary later
}
log.Printf("Loaded opcodes for %d client versions from database", len(om.versions))
return nil
}
// LoadDefaultOpcodes loads hardcoded opcodes for when database is not available
func (om *OpcodeManager) LoadDefaultOpcodes() {
om.mu.Lock()
defer om.mu.Unlock()
// Default version range (1119 is a common EQ2 client version)
defaultVersion := int32(1119)
om.versions[defaultVersion] = defaultVersion
// These are the default opcodes from the C++ implementation
opcodes := map[string]uint16{
"OP_LoginRequestMsg": 0x0001,
"OP_LoginByNumRequestMsg": 0x0002,
"OP_WSLoginRequestMsg": 0x0003,
"OP_ESLoginRequestMsg": 0x0004,
"OP_LoginReplyMsg": 0x0005,
"OP_WorldListMsg": 0x0006,
"OP_WorldStatusChangeMsg": 0x0007,
"OP_AllWSDescRequestMsg": 0x0008,
"OP_WSStatusReplyMsg": 0x0009,
"OP_AllCharactersDescRequestMsg": 0x000A,
"OP_AllCharactersDescReplyMsg": 0x000B,
"OP_CreateCharacterRequestMsg": 0x000C,
"OP_ReskinCharacterRequestMsg": 0x000D,
"OP_CreateCharacterReplyMsg": 0x000E,
"OP_WSCreateCharacterRequestMsg": 0x000F,
"OP_WSCreateCharacterReplyMsg": 0x0010,
"OP_DeleteCharacterRequestMsg": 0x0011,
"OP_DeleteCharacterReplyMsg": 0x0012,
"OP_PlayCharacterRequestMsg": 0x0013,
"OP_PlayCharacterReplyMsg": 0x0014,
"OP_ServerPlayCharacterRequestMsg": 0x0015,
"OP_ServerPlayCharacterReplyMsg": 0x0016,
"OP_KeymapLoadMsg": 0x0017,
"OP_KeymapNoneMsg": 0x0018,
"OP_KeymapDataMsg": 0x0019,
"OP_KeymapSaveMsg": 0x001A,
"OP_LSCheckAcctLockMsg": 0x001B,
"OP_WSAcctLockStatusMsg": 0x001C,
"OP_LsRequestClientCrashLogMsg": 0x001D,
"OP_LsClientBaselogReplyMsg": 0x001E,
"OP_LsClientCrashlogReplyMsg": 0x001F,
"OP_LsClientAlertlogReplyMsg": 0x0020,
"OP_LsClientVerifylogReplyMsg": 0x0021,
"OP_BadLanguageFilter": 0x0022,
"OP_WSServerLockMsg": 0x0023,
"OP_WSServerHideMsg": 0x0024,
"OP_LSServerLockMsg": 0x0025,
"OP_UpdateCharacterSheetMsg": 0x0026,
"OP_UpdateInventoryMsg": 0x0027,
}
internal := make(map[uint16]InternalOpcode)
for name, opcode := range opcodes {
if internalOp, ok := nameToInternalOpcode[name]; ok {
internal[opcode] = internalOp
}
}
om.opcodes[defaultVersion] = opcodes
om.internalOpcodes[defaultVersion] = internal
log.Printf("Loaded default opcodes for version %d", defaultVersion)
}
// GetOpcodeVersion returns the version range start for a given client version
// This implements the same logic as the C++ GetOpcodeVersion function
func (om *OpcodeManager) GetOpcodeVersion(clientVersion int32) int32 {
om.mu.RLock()
defer om.mu.RUnlock()
for versionStart, versionEnd := range om.versions {
if clientVersion >= versionStart && clientVersion <= versionEnd {
return versionStart
}
}
// If no match found, return the client version itself
return clientVersion
}
// GetOpcodeByName returns the wire opcode value for a given opcode name and client version
func (om *OpcodeManager) GetOpcodeByName(clientVersion int32, name string) (uint16, bool) {
om.mu.RLock()
defer om.mu.RUnlock()
version := om.GetOpcodeVersion(clientVersion)
if opcodes, ok := om.opcodes[version]; ok {
if opcode, ok := opcodes[name]; ok {
return opcode, true
}
}
return 0, false
}
// GetInternalOpcode converts a wire opcode to an internal opcode for a given client version
func (om *OpcodeManager) GetInternalOpcode(clientVersion int32, wireOpcode uint16) (InternalOpcode, bool) {
om.mu.RLock()
defer om.mu.RUnlock()
version := om.GetOpcodeVersion(clientVersion)
if internal, ok := om.internalOpcodes[version]; ok {
if internalOp, ok := internal[wireOpcode]; ok {
return internalOp, true
}
}
return OP_Unknown, false
}
// GetWireOpcode converts an internal opcode to a wire opcode for a given client version
func (om *OpcodeManager) GetWireOpcode(clientVersion int32, internalOpcode InternalOpcode) (uint16, bool) {
om.mu.RLock()
defer om.mu.RUnlock()
// Get the name for this internal opcode
name := ""
for n, op := range nameToInternalOpcode {
if op == internalOpcode {
name = n
break
}
}
if name == "" {
return 0, false
}
return om.GetOpcodeByName(clientVersion, name)
}
// nameToInternalOpcode maps opcode names to internal opcodes
var nameToInternalOpcode = map[string]InternalOpcode{
"OP_LoginRequestMsg": OP_LoginRequestMsg,
"OP_LoginByNumRequestMsg": OP_LoginByNumRequestMsg,
"OP_WSLoginRequestMsg": OP_WSLoginRequestMsg,
"OP_LoginReplyMsg": OP_LoginReplyMsg,
"OP_PlayCharacterRequestMsg": OP_PlayCharacterRequest,
"OP_CreateCharacterRequestMsg": OP_CharacterCreate,
"OP_DeleteCharacterRequestMsg": OP_CharacterDelete,
"OP_AllCharactersDescRequestMsg": OP_CharacterList,
"OP_WorldListMsg": OP_ServerList,
"OP_WorldStatusChangeMsg": OP_SendServerStatus,
// Add more mappings as needed
}