217 lines
5.3 KiB
Go
217 lines
5.3 KiB
Go
package cps
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
// Compiler compiles packet definitions into optimized structures
|
|
type Compiler struct {
|
|
packets map[string]*PacketDef
|
|
}
|
|
|
|
// NewCompiler creates a compiler with parsed packet definitions
|
|
func NewCompiler(packets map[string]*PacketDef) *Compiler {
|
|
return &Compiler{packets: packets}
|
|
}
|
|
|
|
// Compile compiles a packet structure for a specific client version
|
|
func (c *Compiler) Compile(packetName string, clientVersion int) (*CompiledStruct, error) {
|
|
packet, exists := c.packets[packetName]
|
|
if !exists {
|
|
return nil, fmt.Errorf("packet %s not found", packetName)
|
|
}
|
|
|
|
// Find best matching version (highest <= clientVersion)
|
|
var bestVersion int = -1
|
|
for version := range packet.Versions {
|
|
if version <= clientVersion && version > bestVersion {
|
|
bestVersion = version
|
|
}
|
|
}
|
|
|
|
if bestVersion == -1 {
|
|
return nil, fmt.Errorf("no compatible version found for %s (client version %d)", packetName, clientVersion)
|
|
}
|
|
|
|
// Build cascaded field list
|
|
fields, fieldOrder, err := c.buildCascadedFields(packet, bestVersion)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Resolve positions and create final structure
|
|
compiledFields, totalSize := c.resolvePositions(fields, fieldOrder)
|
|
|
|
return &CompiledStruct{
|
|
Name: packetName,
|
|
Version: bestVersion,
|
|
Fields: compiledFields,
|
|
Size: totalSize,
|
|
}, nil
|
|
}
|
|
|
|
// buildCascadedFields builds the final field list by cascading from v1 to target version
|
|
func (c *Compiler) buildCascadedFields(packet *PacketDef, targetVersion int) (map[string]*FieldDef, []string, error) {
|
|
result := make(map[string]*FieldDef)
|
|
var allFieldOrder []string
|
|
|
|
// Get all versions up to target, sorted
|
|
var versions []int
|
|
for version := range packet.Versions {
|
|
if version <= targetVersion {
|
|
versions = append(versions, version)
|
|
}
|
|
}
|
|
sort.Ints(versions)
|
|
|
|
// Apply each version in order
|
|
for _, version := range versions {
|
|
versionDef := packet.Versions[version]
|
|
|
|
// Add new fields in their original order
|
|
for _, fieldName := range versionDef.FieldOrder {
|
|
fieldDef := versionDef.Fields[fieldName]
|
|
if fieldDef.Remove {
|
|
delete(result, fieldName)
|
|
// Remove from order list
|
|
for i, name := range allFieldOrder {
|
|
if name == fieldName {
|
|
allFieldOrder = append(allFieldOrder[:i], allFieldOrder[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
// Create copy to avoid modifying original
|
|
newField := *fieldDef
|
|
|
|
// If field exists, preserve position if not explicitly set
|
|
if existing, exists := result[fieldName]; exists {
|
|
if newField.Position == -1 && existing.Position >= 0 {
|
|
newField.Position = existing.Position
|
|
}
|
|
if newField.After == "" && existing.After != "" {
|
|
newField.After = existing.After
|
|
}
|
|
if newField.Before == "" && existing.Before != "" {
|
|
newField.Before = existing.Before
|
|
}
|
|
} else {
|
|
// New field - add to order
|
|
allFieldOrder = append(allFieldOrder, fieldName)
|
|
}
|
|
result[fieldName] = &newField
|
|
}
|
|
}
|
|
}
|
|
|
|
return result, allFieldOrder, nil
|
|
}
|
|
|
|
// resolvePositions resolves field positions and creates ordered field list
|
|
func (c *Compiler) resolvePositions(fields map[string]*FieldDef, originalOrder []string) ([]*CompiledField, int) {
|
|
var compiledFields []*CompiledField
|
|
var fieldOrder []string
|
|
|
|
// Handle explicitly positioned fields first
|
|
positioned := make(map[int]string)
|
|
var maxPos int
|
|
|
|
for name, field := range fields {
|
|
if field.Position >= 0 {
|
|
positioned[field.Position] = name
|
|
if field.Position > maxPos {
|
|
maxPos = field.Position
|
|
}
|
|
}
|
|
}
|
|
|
|
// Build initial order from positioned fields
|
|
for i := 0; i <= maxPos; i++ {
|
|
if name, exists := positioned[i]; exists {
|
|
fieldOrder = append(fieldOrder, name)
|
|
}
|
|
}
|
|
|
|
// Handle @after/@before positioning
|
|
for _, name := range originalOrder {
|
|
field := fields[name]
|
|
if field.Position >= 0 {
|
|
continue // Already handled
|
|
}
|
|
|
|
if field.After != "" {
|
|
// Insert after referenced field
|
|
for i, existing := range fieldOrder {
|
|
if existing == field.After {
|
|
fieldOrder = append(fieldOrder[:i+1], append([]string{name}, fieldOrder[i+1:]...)...)
|
|
break
|
|
}
|
|
}
|
|
} else if field.Before != "" {
|
|
// Insert before referenced field
|
|
for i, existing := range fieldOrder {
|
|
if existing == field.Before {
|
|
fieldOrder = append(fieldOrder[:i], append([]string{name}, fieldOrder[i:]...)...)
|
|
break
|
|
}
|
|
}
|
|
} else {
|
|
// Append in original order
|
|
fieldOrder = append(fieldOrder, name)
|
|
}
|
|
}
|
|
|
|
// Create compiled fields with calculated offsets
|
|
offset := 0
|
|
dynamic := false
|
|
|
|
for _, name := range fieldOrder {
|
|
field := fields[name]
|
|
|
|
compiledField := &CompiledField{
|
|
Name: name,
|
|
Type: field.Type,
|
|
Size: field.Size,
|
|
Offset: offset,
|
|
Dynamic: field.Size == -1,
|
|
}
|
|
|
|
compiledFields = append(compiledFields, compiledField)
|
|
|
|
// Calculate offset for next field
|
|
if field.Size == -1 {
|
|
dynamic = true
|
|
} else if !dynamic {
|
|
fieldSize := c.getTypeSize(field.Type)
|
|
if field.Size > 0 {
|
|
fieldSize *= field.Size
|
|
}
|
|
offset += fieldSize
|
|
}
|
|
}
|
|
|
|
totalSize := offset
|
|
if dynamic {
|
|
totalSize = -1
|
|
}
|
|
|
|
return compiledFields, totalSize
|
|
}
|
|
|
|
// getTypeSize returns the byte size of a data type
|
|
func (c *Compiler) getTypeSize(t EQ2Type) int {
|
|
switch t {
|
|
case TypeInt8:
|
|
return 1
|
|
case TypeInt16:
|
|
return 2
|
|
case TypeInt32, TypeFloat32, TypeColor:
|
|
return 4
|
|
case TypeInt64:
|
|
return 8
|
|
default:
|
|
return -1 // Variable size
|
|
}
|
|
}
|