258 lines
5.5 KiB
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
|
|
}
|