implement packet field templates
This commit is contained in:
parent
b2a2e9366b
commit
19bc67233b
@ -2,9 +2,7 @@
|
|||||||
<version number="1">
|
<version number="1">
|
||||||
<i16 name="num_words" oversized="255">
|
<i16 name="num_words" oversized="255">
|
||||||
<array name="words_array" count="var:num_words">
|
<array name="words_array" count="var:num_words">
|
||||||
<substruct>
|
<str16 name="word">
|
||||||
<str16 name="word">
|
|
||||||
</substruct>
|
|
||||||
</array>
|
</array>
|
||||||
</version>
|
</version>
|
||||||
</packet>
|
</packet>
|
@ -8,7 +8,6 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Object pools for reducing allocations
|
|
||||||
var (
|
var (
|
||||||
fieldOrderPool = sync.Pool{
|
fieldOrderPool = sync.Pool{
|
||||||
New: func() any {
|
New: func() any {
|
||||||
@ -29,7 +28,6 @@ var (
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// String builder for efficient concatenation
|
|
||||||
type stringBuilder struct {
|
type stringBuilder struct {
|
||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
@ -52,6 +50,7 @@ type Parser struct {
|
|||||||
current *Token
|
current *Token
|
||||||
input string
|
input string
|
||||||
substructs map[string]*PacketDef
|
substructs map[string]*PacketDef
|
||||||
|
templates map[string]*PacketDef
|
||||||
tagStack []string
|
tagStack []string
|
||||||
fieldNames []string
|
fieldNames []string
|
||||||
}
|
}
|
||||||
@ -68,7 +67,7 @@ var typeMap = map[string]common.EQ2DataType{
|
|||||||
"si64": common.TypeSInt64,
|
"si64": common.TypeSInt64,
|
||||||
"f32": common.TypeFloat,
|
"f32": common.TypeFloat,
|
||||||
"f64": common.TypeDouble,
|
"f64": common.TypeDouble,
|
||||||
"double": common.TypeDouble, // XML compatibility
|
"double": common.TypeDouble,
|
||||||
"str8": common.TypeString8,
|
"str8": common.TypeString8,
|
||||||
"str16": common.TypeString16,
|
"str16": common.TypeString16,
|
||||||
"str32": common.TypeString32,
|
"str32": common.TypeString32,
|
||||||
@ -84,6 +83,7 @@ func NewParser(input string) *Parser {
|
|||||||
lexer: NewLexer(input),
|
lexer: NewLexer(input),
|
||||||
input: input,
|
input: input,
|
||||||
substructs: make(map[string]*PacketDef),
|
substructs: make(map[string]*PacketDef),
|
||||||
|
templates: make(map[string]*PacketDef),
|
||||||
tagStack: make([]string, 0, 16),
|
tagStack: make([]string, 0, 16),
|
||||||
fieldNames: make([]string, 0, 8),
|
fieldNames: make([]string, 0, 8),
|
||||||
}
|
}
|
||||||
@ -285,14 +285,6 @@ func combineConditions(cond1, cond2 string) string {
|
|||||||
return string(buf)
|
return string(buf)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates packet definition with estimated capacity
|
|
||||||
func NewPacketDef(estimatedFields int) *PacketDef {
|
|
||||||
return &PacketDef{
|
|
||||||
Fields: make(map[string]FieldDesc, estimatedFields),
|
|
||||||
Orders: make(map[uint32][]string, 4),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parses the entire PML document
|
// Parses the entire PML document
|
||||||
func (p *Parser) Parse() (map[string]*PacketDef, error) {
|
func (p *Parser) Parse() (map[string]*PacketDef, error) {
|
||||||
packets := make(map[string]*PacketDef)
|
packets := make(map[string]*PacketDef)
|
||||||
@ -322,6 +314,16 @@ func (p *Parser) Parse() (map[string]*PacketDef, error) {
|
|||||||
if name != "" {
|
if name != "" {
|
||||||
p.substructs[name] = substruct
|
p.substructs[name] = substruct
|
||||||
}
|
}
|
||||||
|
case "template":
|
||||||
|
name := p.current.Attributes["name"]
|
||||||
|
if name != "" {
|
||||||
|
err := p.parseTemplateDefinition(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
p.advance()
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
p.advance()
|
p.advance()
|
||||||
}
|
}
|
||||||
@ -468,6 +470,11 @@ func (p *Parser) parseElements(packetDef *PacketDef, fieldOrder *[]string, prefi
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
case "template":
|
||||||
|
err := p.parseTemplate(packetDef, fieldOrder, prefix)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
err := p.parseField(packetDef, fieldOrder, prefix)
|
err := p.parseField(packetDef, fieldOrder, prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -483,6 +490,90 @@ func (p *Parser) parseElements(packetDef *PacketDef, fieldOrder *[]string, prefi
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handles template definition and usage
|
||||||
|
func (p *Parser) parseTemplate(packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
|
||||||
|
attrs := p.current.Attributes
|
||||||
|
|
||||||
|
// Template definition: <template name="foo">
|
||||||
|
if templateName := attrs["name"]; templateName != "" {
|
||||||
|
return p.parseTemplateDefinition(templateName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Template usage: <template use="foo">
|
||||||
|
if templateUse := attrs["use"]; templateUse != "" {
|
||||||
|
return p.parseTemplateUsage(packetDef, fieldOrder, prefix, templateUse)
|
||||||
|
}
|
||||||
|
|
||||||
|
// No name or use attribute - skip
|
||||||
|
p.advance()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parses template definition and stores it
|
||||||
|
func (p *Parser) parseTemplateDefinition(templateName string) error {
|
||||||
|
templateDef := NewPacketDef(16)
|
||||||
|
fieldOrder := fieldOrderPool.Get().(*[]string)
|
||||||
|
*fieldOrder = (*fieldOrder)[:0]
|
||||||
|
defer fieldOrderPool.Put(fieldOrder)
|
||||||
|
|
||||||
|
if p.current.Type == TokenSelfCloseTag {
|
||||||
|
p.advance()
|
||||||
|
templateDef.Orders[1] = make([]string, len(*fieldOrder))
|
||||||
|
copy(templateDef.Orders[1], *fieldOrder)
|
||||||
|
p.templates[templateName] = templateDef
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
p.pushTag("template")
|
||||||
|
p.advance()
|
||||||
|
|
||||||
|
err := p.parseElements(templateDef, fieldOrder, "")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.current.Type == TokenCloseTag && p.current.Tag(p.input) == "template" {
|
||||||
|
if err := p.popTag("template"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
p.advance()
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("expected closing tag for template at line %d", p.current.Line)
|
||||||
|
}
|
||||||
|
|
||||||
|
templateDef.Orders[1] = make([]string, len(*fieldOrder))
|
||||||
|
copy(templateDef.Orders[1], *fieldOrder)
|
||||||
|
p.templates[templateName] = templateDef
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Injects template fields into current parsing context
|
||||||
|
func (p *Parser) parseTemplateUsage(packetDef *PacketDef, fieldOrder *[]string, prefix string, templateName string) error {
|
||||||
|
template, exists := p.templates[templateName]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("undefined template '%s' at line %d", templateName, p.current.Line)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject all template fields into current packet
|
||||||
|
if templateOrder, exists := template.Orders[1]; exists {
|
||||||
|
for _, fieldName := range templateOrder {
|
||||||
|
if fieldDesc, exists := template.Fields[fieldName]; exists {
|
||||||
|
// Apply prefix if provided
|
||||||
|
fullName := fieldName
|
||||||
|
if prefix != "" {
|
||||||
|
fullName = buildFieldName(prefix, fieldName)
|
||||||
|
}
|
||||||
|
|
||||||
|
packetDef.Fields[fullName] = fieldDesc
|
||||||
|
*fieldOrder = append(*fieldOrder, fullName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.advance()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Handles group elements
|
// Handles group elements
|
||||||
func (p *Parser) parseGroup(packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
|
func (p *Parser) parseGroup(packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
|
||||||
attrs := p.current.Attributes
|
attrs := p.current.Attributes
|
||||||
@ -520,7 +611,7 @@ func (p *Parser) parseGroup(packetDef *PacketDef, fieldOrder *[]string, prefix s
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles array elements - FIXED: Remove redundant substruct wrapper
|
// Handles array elements
|
||||||
func (p *Parser) parseArray(packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
|
func (p *Parser) parseArray(packetDef *PacketDef, fieldOrder *[]string, prefix string) error {
|
||||||
attrs := p.current.Attributes
|
attrs := p.current.Attributes
|
||||||
|
|
||||||
|
@ -720,6 +720,102 @@ func TestErrorHandling(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestTemplateDefinitionAndUsage(t *testing.T) {
|
||||||
|
pml := `<!-- Define reusable templates -->
|
||||||
|
<template name="position">
|
||||||
|
<f32 name="x,y,z">
|
||||||
|
<f32 name="heading">
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template name="appearance">
|
||||||
|
<color name="skin_color,hair_color,eye_color">
|
||||||
|
<str16 name="face_file,hair_file">
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- Use templates in packet -->
|
||||||
|
<packet name="PlayerUpdate">
|
||||||
|
<version number="1">
|
||||||
|
<i32 name="player_id">
|
||||||
|
<template use="position">
|
||||||
|
<i8 name="level">
|
||||||
|
<template use="appearance">
|
||||||
|
<str16 name="guild_name" if="flag:has_guild">
|
||||||
|
</version>
|
||||||
|
</packet>
|
||||||
|
|
||||||
|
<!-- Test template with group prefix -->
|
||||||
|
<packet name="GroupedTemplate">
|
||||||
|
<version number="1">
|
||||||
|
<i32 name="entity_id">
|
||||||
|
<group name="current">
|
||||||
|
<template use="position">
|
||||||
|
</group>
|
||||||
|
<group name="target">
|
||||||
|
<template use="position">
|
||||||
|
</group>
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
packets, err := Parse(pml)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Parse failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test basic template injection
|
||||||
|
playerPacket := packets["PlayerUpdate"]
|
||||||
|
if playerPacket == nil {
|
||||||
|
t.Fatal("PlayerUpdate packet not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that template fields were injected
|
||||||
|
expectedFields := []string{"player_id", "x", "y", "z", "heading", "level", "skin_color", "hair_color", "eye_color", "face_file", "hair_file", "guild_name"}
|
||||||
|
if len(playerPacket.Fields) != len(expectedFields) {
|
||||||
|
t.Errorf("Expected %d fields, got %d", len(expectedFields), len(playerPacket.Fields))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify specific template fields exist with correct types
|
||||||
|
if playerPacket.Fields["x"].Type != common.TypeFloat {
|
||||||
|
t.Error("x should be TypeFloat from position template")
|
||||||
|
}
|
||||||
|
if playerPacket.Fields["heading"].Type != common.TypeFloat {
|
||||||
|
t.Error("heading should be TypeFloat from position template")
|
||||||
|
}
|
||||||
|
if playerPacket.Fields["skin_color"].Type != common.TypeColor {
|
||||||
|
t.Error("skin_color should be TypeColor from appearance template")
|
||||||
|
}
|
||||||
|
if playerPacket.Fields["face_file"].Type != common.TypeString16 {
|
||||||
|
t.Error("face_file should be TypeString16 from appearance template")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check field ordering preserves template injection points
|
||||||
|
order := playerPacket.Orders[1]
|
||||||
|
expectedOrder := []string{"player_id", "x", "y", "z", "heading", "level", "skin_color", "hair_color", "eye_color", "face_file", "hair_file", "guild_name"}
|
||||||
|
if !equalSlices(order, expectedOrder) {
|
||||||
|
t.Errorf("Expected order %v, got %v", expectedOrder, order)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test template with group prefixes
|
||||||
|
groupedPacket := packets["GroupedTemplate"]
|
||||||
|
if groupedPacket == nil {
|
||||||
|
t.Fatal("GroupedTemplate packet not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check prefixed fields from templates
|
||||||
|
if groupedPacket.Fields["current_x"].Type != common.TypeFloat {
|
||||||
|
t.Error("current_x should exist from prefixed template")
|
||||||
|
}
|
||||||
|
if groupedPacket.Fields["target_heading"].Type != common.TypeFloat {
|
||||||
|
t.Error("target_heading should exist from prefixed template")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify grouped template field order
|
||||||
|
groupedOrder := groupedPacket.Orders[1]
|
||||||
|
expectedGroupedOrder := []string{"entity_id", "current_x", "current_y", "current_z", "current_heading", "target_x", "target_y", "target_z", "target_heading"}
|
||||||
|
if !equalSlices(groupedOrder, expectedGroupedOrder) {
|
||||||
|
t.Errorf("Expected grouped order %v, got %v", expectedGroupedOrder, groupedOrder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func BenchmarkSimplePacket(b *testing.B) {
|
func BenchmarkSimplePacket(b *testing.B) {
|
||||||
pml := `<packet name="Simple">
|
pml := `<packet name="Simple">
|
||||||
<version number="1">
|
<version number="1">
|
||||||
@ -738,27 +834,53 @@ func BenchmarkSimplePacket(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkComplexPacketWithNewFeatures(b *testing.B) {
|
func BenchmarkMediumPacket(b *testing.B) {
|
||||||
pml := `<packet name="Complex">
|
pml := `<packet name="Medium">
|
||||||
<version number="562">
|
<version number="1">
|
||||||
<i32 name="player_id,account_id">
|
<i32 name="id,account_id,session_id">
|
||||||
<str16 name="player_name">
|
<str16 name="name,guild_name">
|
||||||
<color name="skin_color,hair_color,eye_color">
|
<i8 name="level,race,class">
|
||||||
<str16 name="guild_name" if="flag:has_guild">
|
<f32 name="x,y,z,heading">
|
||||||
<i32 name="guild_id" if="flag:has_guild">
|
<color name="skin_color,hair_color">
|
||||||
<i8 name="equipment_count">
|
</version>
|
||||||
<array name="equipment" count="var:equipment_count" max_size="50">
|
</packet>`
|
||||||
<i16 name="slot_id,item_type">
|
|
||||||
<color name="primary_color,secondary_color">
|
for b.Loop() {
|
||||||
<i8 name="enhancement_level" if="item_type!=0">
|
_, err := Parse(pml)
|
||||||
<i32 name="stat_value" type2="f32" type2_if="item_type==6" oversized="255">
|
if err != nil {
|
||||||
</array>
|
b.Fatal(err)
|
||||||
<i8 name="stat_count">
|
}
|
||||||
<array name="stats" count="var:stat_count" optional="true">
|
}
|
||||||
<i8 name="stat_type">
|
}
|
||||||
<i32 name="base_value">
|
|
||||||
<i32 name="modified_value" if="stat_type>=1&stat_type<=5">
|
func BenchmarkLargePacket(b *testing.B) {
|
||||||
<f64 name="percentage" if="stat_type==6">
|
pml := `<packet name="Large">
|
||||||
|
<version number="1">
|
||||||
|
<i32 name="a1,a2,a3,a4,a5,a6,a7,a8,a9,a10">
|
||||||
|
<str16 name="s1,s2,s3,s4,s5">
|
||||||
|
<f32 name="f1,f2,f3,f4,f5,f6,f7,f8">
|
||||||
|
<color name="c1,c2,c3,c4">
|
||||||
|
<i8 name="b1,b2,b3,b4,b5,b6,b7,b8,b9,b10,b11,b12">
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
for b.Loop() {
|
||||||
|
_, err := Parse(pml)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkArrayPacket(b *testing.B) {
|
||||||
|
pml := `<packet name="ArrayPacket">
|
||||||
|
<version number="1">
|
||||||
|
<i8 name="count">
|
||||||
|
<array name="items" count="var:count">
|
||||||
|
<i32 name="id">
|
||||||
|
<str16 name="name">
|
||||||
|
<color name="color">
|
||||||
|
<f32 name="x,y,z">
|
||||||
</array>
|
</array>
|
||||||
</version>
|
</version>
|
||||||
</packet>`
|
</packet>`
|
||||||
@ -771,6 +893,33 @@ func BenchmarkComplexPacketWithNewFeatures(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func BenchmarkMultiVersionPacket(b *testing.B) {
|
||||||
|
pml := `<packet name="MultiVersion">
|
||||||
|
<version number="1">
|
||||||
|
<i32 name="id">
|
||||||
|
<str16 name="name">
|
||||||
|
</version>
|
||||||
|
<version number="100">
|
||||||
|
<i32 name="id">
|
||||||
|
<str16 name="name">
|
||||||
|
<i8 name="level">
|
||||||
|
</version>
|
||||||
|
<version number="500">
|
||||||
|
<i32 name="id">
|
||||||
|
<str16 name="name">
|
||||||
|
<i8 name="level">
|
||||||
|
<color name="color">
|
||||||
|
</version>
|
||||||
|
</packet>`
|
||||||
|
|
||||||
|
for b.Loop() {
|
||||||
|
_, err := Parse(pml)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Helper function to compare slices
|
// Helper function to compare slices
|
||||||
func equalSlices(a, b []string) bool {
|
func equalSlices(a, b []string) bool {
|
||||||
if len(a) != len(b) {
|
if len(a) != len(b) {
|
||||||
|
@ -8,6 +8,14 @@ type PacketDef struct {
|
|||||||
Orders map[uint32][]string // Field order by version number
|
Orders map[uint32][]string // Field order by version number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Creates packet definition with estimated capacity
|
||||||
|
func NewPacketDef(estimatedFields int) *PacketDef {
|
||||||
|
return &PacketDef{
|
||||||
|
Fields: make(map[string]FieldDesc, estimatedFields),
|
||||||
|
Orders: make(map[uint32][]string, 4),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (def *PacketDef) Parse(data []byte, version uint32, flags uint64) (map[string]any, error) {
|
func (def *PacketDef) Parse(data []byte, version uint32, flags uint64) (map[string]any, error) {
|
||||||
ctx := NewContext(data, version, flags)
|
ctx := NewContext(data, version, flags)
|
||||||
return def.parseStruct(ctx)
|
return def.parseStruct(ctx)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user