297 lines
7.2 KiB
Go
297 lines
7.2 KiB
Go
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
|
|
} |