package defs //go:generate go run gen/registration.go import ( "fmt" "reflect" "sort" "strings" "sync" "git.sharkk.net/EQ2/Protocol/opcodes" ) // PacketStruct represents a packet structure definition type PacketStruct struct { Name string Version uint16 OpcodeName string StructType reflect.Type } // PacketManager manages packet structure lookups for specific client versions type PacketManager struct { version uint16 structs map[string][]*PacketStruct // map[name][]versions opcodeToName map[opcodes.EmuOpcode]string nameToStruct map[string]*PacketStruct // cached lookups for this version mu sync.RWMutex } // NewPacketManager creates a new packet manager for the specified version func NewPacketManager(version uint16) *PacketManager { pm := &PacketManager{ version: version, structs: make(map[string][]*PacketStruct), opcodeToName: make(map[opcodes.EmuOpcode]string), nameToStruct: make(map[string]*PacketStruct), } pm.initializePacketMappings() return pm } // initializePacketMappings is generated in registrations_generated.go // registerStruct adds a packet structure definition func (pm *PacketManager) registerStruct(name string, version uint16, opcodeName string, structType reflect.Type) { ps := &PacketStruct{ Name: name, Version: version, OpcodeName: opcodeName, StructType: structType, } pm.mu.Lock() defer pm.mu.Unlock() if _, exists := pm.structs[name]; !exists { pm.structs[name] = make([]*PacketStruct, 0) } pm.structs[name] = append(pm.structs[name], ps) // Map opcode to struct name for quick lookup // We'll need to add this mapping based on the actual opcode constants // For now, we map based on the opcode name string pm.mapOpcodeToStruct(opcodeName, name) } // mapOpcodeToStruct maps opcode names to struct names // This should be updated when we have a proper opcode name mapping func (pm *PacketManager) mapOpcodeToStruct(opcodeName string, structName string) { // Map common opcodes - this will be expanded as needed switch opcodeName { case "OP_CreateCharacterRequestMsg": pm.opcodeToName[opcodes.OP_CreateCharacterRequestMsg] = structName case "OP_BadLanguageFilter": pm.opcodeToName[opcodes.OP_BadLanguageFilter] = structName // Login opcodes would need to be handled differently since they use LS_ prefix // We'll need to decide how to handle login vs world opcodes } } // GetStruct returns the best matching packet struct for the given name and version // It finds the highest version that is <= the requested version (like C++ code) func (pm *PacketManager) GetStruct(name string, version uint16) *PacketStruct { pm.mu.RLock() defer pm.mu.RUnlock() // Check cache first cacheKey := fmt.Sprintf("%s_%d", name, version) if cached, ok := pm.nameToStruct[cacheKey]; ok { return cached } versions, exists := pm.structs[name] if !exists { return nil } var bestMatch *PacketStruct for _, ps := range versions { // Find the highest version that's <= requested version if ps.Version <= version { if bestMatch == nil || ps.Version > bestMatch.Version { bestMatch = ps } } } if bestMatch != nil { // Cache the result pm.nameToStruct[cacheKey] = bestMatch } return bestMatch } // GetStructByVersion returns an exact version match (like C++ getStructByVersion) func (pm *PacketManager) GetStructByVersion(name string, version uint16) *PacketStruct { pm.mu.RLock() defer pm.mu.RUnlock() versions, exists := pm.structs[name] if !exists { return nil } for _, ps := range versions { if ps.Version == version { return ps } } return nil } // GetStructByOpcode returns the packet struct for a given opcode func (pm *PacketManager) GetStructByOpcode(opcode opcodes.EmuOpcode) *PacketStruct { pm.mu.RLock() defer pm.mu.RUnlock() name, exists := pm.opcodeToName[opcode] if !exists { return nil } return pm.GetStruct(name, pm.version) } // CreateInstance creates a new instance of the packet struct func (pm *PacketManager) CreateInstance(name string) interface{} { ps := pm.GetStruct(name, pm.version) if ps == nil { return nil } return reflect.New(ps.StructType).Interface() } // CreateInstanceByOpcode creates a new instance by opcode func (pm *PacketManager) CreateInstanceByOpcode(opcode opcodes.EmuOpcode) interface{} { ps := pm.GetStructByOpcode(opcode) if ps == nil { return nil } return reflect.New(ps.StructType).Interface() } // GetAvailableVersions returns all versions available for a struct name func (pm *PacketManager) GetAvailableVersions(name string) []uint16 { pm.mu.RLock() defer pm.mu.RUnlock() versions, exists := pm.structs[name] if !exists { return nil } var result []uint16 for _, ps := range versions { result = append(result, ps.Version) } sort.Slice(result, func(i, j int) bool { return result[i] < result[j] }) return result } // GetAllStructNames returns all registered struct names func (pm *PacketManager) GetAllStructNames() []string { pm.mu.RLock() defer pm.mu.RUnlock() var names []string for name := range pm.structs { names = append(names, name) } sort.Strings(names) return names } // DumpStructs returns a formatted string of all loaded structs for debugging func (pm *PacketManager) DumpStructs() string { pm.mu.RLock() defer pm.mu.RUnlock() var sb strings.Builder sb.WriteString(fmt.Sprintf("Packet Manager for version %d:\n", pm.version)) sb.WriteString("=====================================\n") names := pm.GetAllStructNames() for _, name := range names { versions := pm.GetAvailableVersions(name) sb.WriteString(fmt.Sprintf("%s: versions %v\n", name, versions)) // Show which version would be selected if ps := pm.GetStruct(name, pm.version); ps != nil { sb.WriteString(fmt.Sprintf(" -> Selected version %d for client %d\n", ps.Version, pm.version)) } } return sb.String() } // GetStats returns statistics about loaded packet structs func (pm *PacketManager) GetStats() map[string]int { pm.mu.RLock() defer pm.mu.RUnlock() totalStructs := 0 for _, versions := range pm.structs { totalStructs += len(versions) } return map[string]int{ "version": int(pm.version), "unique_structs": len(pm.structs), "total_versions": totalStructs, "cached_lookups": len(pm.nameToStruct), } } // Global managers cache var ( managers = make(map[uint16]*PacketManager) managersMu sync.RWMutex ) // GetManager returns packet manager for a version func GetManager(version uint16) *PacketManager { managersMu.RLock() if mgr, ok := managers[version]; ok { managersMu.RUnlock() return mgr } managersMu.RUnlock() managersMu.Lock() defer managersMu.Unlock() // Double-check after acquiring write lock if mgr, ok := managers[version]; ok { return mgr } mgr := NewPacketManager(version) managers[version] = mgr return mgr } // ClearCache clears the cached managers (useful for testing) func ClearCache() { managersMu.Lock() defer managersMu.Unlock() managers = make(map[uint16]*PacketManager) } // GetLoadedVersions returns all versions that have managers loaded func GetLoadedVersions() []uint16 { managersMu.RLock() defer managersMu.RUnlock() var versions []uint16 for v := range managers { versions = append(versions, v) } sort.Slice(versions, func(i, j int) bool { return versions[i] < versions[j] }) return versions }