1
0
Protocol/opcodes/manager.go

258 lines
5.5 KiB
Go

package opcodes
//go:generate go run gen/main.go
import (
"database/sql"
"fmt"
"sort"
"strings"
"sync"
)
// OpcodeManager manages opcode translations for a specific client version
type OpcodeManager struct {
version int16
emuToEQ map[EmuOpcode]uint16
eqToEmu map[uint16]EmuOpcode
emuToName map[EmuOpcode]string
eqToName map[uint16]string
nameToEmu map[string]EmuOpcode
mu sync.RWMutex
}
// NewOpcodeManager creates a new opcode manager for the specified version
func NewOpcodeManager(version int16) *OpcodeManager {
om := &OpcodeManager{
version: version,
emuToEQ: make(map[EmuOpcode]uint16),
eqToEmu: make(map[uint16]EmuOpcode),
emuToName: make(map[EmuOpcode]string),
eqToName: make(map[uint16]string),
nameToEmu: make(map[string]EmuOpcode),
}
om.initializeNameMappings() // Generated by go:generate
return om
}
// LoadFromDatabase loads opcode mappings from database
func (om *OpcodeManager) LoadFromDatabase(db *sql.DB, tableName string) error {
query := `
SELECT name, opcode
FROM ` + tableName + `
WHERE ? >= version_range1
AND (version_range2 = 0 OR ? <= version_range2)
ORDER BY version_range1 DESC, version_range2 DESC
`
rows, err := db.Query(query, om.version, om.version)
if err != nil {
return err
}
defer rows.Close()
om.mu.Lock()
defer om.mu.Unlock()
loaded := make(map[string]bool)
for rows.Next() {
var name string
var opcode int16
if err := rows.Scan(&name, &opcode); err != nil {
continue
}
if loaded[name] {
continue
}
loaded[name] = true
eqOpcode := uint16(opcode)
// Only map if we have a constant for this opcode name
if emuOp, ok := om.nameToEmu[name]; ok {
om.emuToEQ[emuOp] = eqOpcode
om.eqToEmu[eqOpcode] = emuOp
om.emuToName[emuOp] = name
om.eqToName[eqOpcode] = name
}
}
// Map missing opcodes to 0xFFFF
for name, emuOp := range om.nameToEmu {
if _, ok := om.emuToEQ[emuOp]; !ok {
om.emuToEQ[emuOp] = 0xFFFF
om.emuToName[emuOp] = name
}
}
return rows.Err()
}
// EmuToEQ converts emulator opcode to EQ opcode
func (om *OpcodeManager) EmuToEQ(emu EmuOpcode) uint16 {
om.mu.RLock()
defer om.mu.RUnlock()
if eq, ok := om.emuToEQ[emu]; ok {
return eq
}
return 0xFFFF
}
// EQToEmu converts EQ opcode to emulator opcode
func (om *OpcodeManager) EQToEmu(eq uint16) EmuOpcode {
om.mu.RLock()
defer om.mu.RUnlock()
if emu, ok := om.eqToEmu[eq]; ok {
return emu
}
return OP_Unknown
}
// EmuToName returns the name of an emulator opcode
func (om *OpcodeManager) EmuToName(emu EmuOpcode) string {
om.mu.RLock()
defer om.mu.RUnlock()
if name, ok := om.emuToName[emu]; ok {
return name
}
return "OP_Unknown"
}
// EQToName returns the name of an EQ opcode
func (om *OpcodeManager) EQToName(eq uint16) string {
om.mu.RLock()
defer om.mu.RUnlock()
if name, ok := om.eqToName[eq]; ok {
return name
}
return "OP_Unknown"
}
// GetMissingOpcodes returns list of opcodes that map to 0xFFFF
func (om *OpcodeManager) GetMissingOpcodes() []string {
om.mu.RLock()
defer om.mu.RUnlock()
var missing []string
for emu, eq := range om.emuToEQ {
if eq == 0xFFFF {
if name, ok := om.emuToName[emu]; ok {
missing = append(missing, name)
}
}
}
return missing
}
// IsValidOpcode checks if an EQ opcode is valid/known
func (om *OpcodeManager) IsValidOpcode(eq uint16) bool {
om.mu.RLock()
defer om.mu.RUnlock()
_, exists := om.eqToEmu[eq]
return exists
}
// DumpOpcodes returns a formatted string of all loaded opcodes for debugging
func (om *OpcodeManager) DumpOpcodes() string {
om.mu.RLock()
defer om.mu.RUnlock()
var sb strings.Builder
sb.WriteString(fmt.Sprintf("Opcode Manager for version %d:\n", om.version))
sb.WriteString("=====================================\n")
var eqOpcodes []uint16
for eq := range om.eqToEmu {
eqOpcodes = append(eqOpcodes, eq)
}
sort.Slice(eqOpcodes, func(i, j int) bool {
return eqOpcodes[i] < eqOpcodes[j]
})
for _, eq := range eqOpcodes {
emu := om.eqToEmu[eq]
name := om.eqToName[eq]
sb.WriteString(fmt.Sprintf("0x%04x -> 0x%04x : %s\n", eq, emu, name))
}
return sb.String()
}
// GetStats returns statistics about loaded opcodes
func (om *OpcodeManager) GetStats() map[string]int {
om.mu.RLock()
defer om.mu.RUnlock()
return map[string]int{
"version": int(om.version),
"total_opcodes": len(om.eqToEmu),
"named_opcodes": len(om.emuToName),
"mapped_opcodes": len(om.emuToEQ),
}
}
// Global managers cache
var (
managers = make(map[int16]*OpcodeManager)
managersMu sync.RWMutex
)
// GetManager returns opcode manager for a version
func GetManager(version int16, db *sql.DB, tableName string) *OpcodeManager {
managersMu.RLock()
if mgr, ok := managers[version]; ok {
managersMu.RUnlock()
return mgr
}
managersMu.RUnlock()
managersMu.Lock()
defer managersMu.Unlock()
if mgr, ok := managers[version]; ok {
return mgr
}
mgr := NewOpcodeManager(version)
if db != nil {
if err := mgr.LoadFromDatabase(db, tableName); err != nil {
fmt.Printf("Warning: Failed to load opcodes for version %d: %v\n", version, err)
}
}
managers[version] = mgr
return mgr
}
// ClearCache clears the cached managers (useful for testing)
func ClearCache() {
managersMu.Lock()
defer managersMu.Unlock()
managers = make(map[int16]*OpcodeManager)
}
// GetLoadedVersions returns all versions that have managers loaded
func GetLoadedVersions() []int16 {
managersMu.RLock()
defer managersMu.RUnlock()
var versions []int16
for v := range managers {
versions = append(versions, v)
}
sort.Slice(versions, func(i, j int) bool {
return versions[i] < versions[j]
})
return versions
}