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 }