239 lines
7.3 KiB
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
|
|
} |