swap for loops to range, add docs for packet parser
This commit is contained in:
parent
672bbd7629
commit
3c844ca33a
131
internal/packets/parser/DOCS.md
Normal file
131
internal/packets/parser/DOCS.md
Normal file
@ -0,0 +1,131 @@
|
||||
# EQ2 Packet Parser
|
||||
|
||||
A dead-simple way to turn EverQuest 2's binary packet data into Go structs. Just add some tags to your struct fields and let reflection do the heavy lifting.
|
||||
|
||||
## How it works
|
||||
|
||||
```go
|
||||
// Your packet structure
|
||||
type LoginPacket struct {
|
||||
UserID uint32 `eq2:"int32"`
|
||||
Username common.EQ2String16 `eq2:"string16"`
|
||||
Level uint8 `eq2:"int8"`
|
||||
}
|
||||
|
||||
// Parse some bytes
|
||||
data := []byte{...} // whatever bytes you got
|
||||
parser := parser.NewParser(data)
|
||||
|
||||
var packet LoginPacket
|
||||
err := parser.ParseStruct(&packet)
|
||||
// boom, packet is now filled with data
|
||||
```
|
||||
|
||||
## Tag Syntax
|
||||
|
||||
Just slap `eq2:"type,options"` on your fields and you're good to go.
|
||||
|
||||
### The usual suspects
|
||||
```go
|
||||
PlayerID uint32 `eq2:"int32"` // 32-bit integer
|
||||
Health uint16 `eq2:"int16"` // 16-bit integer
|
||||
Alive uint8 `eq2:"int8"` // 8-bit integer
|
||||
Damage float32 `eq2:"float"` // 32-bit float
|
||||
Name common.EQ2String16 `eq2:"string16"` // EQ2's weird string format
|
||||
Color common.EQ2Color `eq2:"color"` // RGB color
|
||||
```
|
||||
|
||||
### Arrays (because everything's an array in EQ2)
|
||||
```go
|
||||
// Fixed size - always 5 items
|
||||
Stats []uint16 `eq2:"int16,len=5"`
|
||||
|
||||
// Dynamic size - read ItemCount first, then that many items
|
||||
ItemCount uint8 `eq2:"int8"`
|
||||
Items []Item `eq2:"array,arraysize=ItemCount"`
|
||||
```
|
||||
|
||||
### Conditionals (the fun stuff)
|
||||
```go
|
||||
// Only parse this if Channel equals 1
|
||||
MessageType uint8 `eq2:"int8,if=Channel==1"`
|
||||
|
||||
// Only parse if HasRewards is truthy
|
||||
HasRewards uint8 `eq2:"int8"`
|
||||
RewardData *RewardInfo `eq2:"substruct,ifvariableset=HasRewards"`
|
||||
|
||||
// Only parse if we set the "has_equipment" flag
|
||||
Equipment []Equipment `eq2:"equipment,ifflag=has_equipment"`
|
||||
```
|
||||
|
||||
### Type switching (when EQ2 reuses the same bytes for different things)
|
||||
```go
|
||||
// Normally parse as int32, but if StatType != 6, parse as float instead
|
||||
StatType uint8 `eq2:"int8"`
|
||||
StatValue any `eq2:"int32,type2=float,type2criteria=StatType!=6"`
|
||||
```
|
||||
|
||||
### Size limits (because EQ2 packets can get weird)
|
||||
```go
|
||||
// If the data is bigger than 1000 bytes, just truncate it
|
||||
DataSize uint16 `eq2:"int16"`
|
||||
Data []byte `eq2:"char,len=DataSize,maxsize=1000,skipoversized"`
|
||||
```
|
||||
|
||||
## Multiple client versions
|
||||
|
||||
EQ2 has like 50 different client versions, each with slightly different packet layouts. Handle it like this:
|
||||
|
||||
```go
|
||||
registry := parser.NewVersionRegistry()
|
||||
registry.RegisterStruct("LoginReply", "1.0", reflect.TypeOf(LoginReplyV1{}))
|
||||
registry.RegisterStruct("LoginReply", "2.0", reflect.TypeOf(LoginReplyV2{}))
|
||||
|
||||
// Parser picks the right version (or falls back to closest match)
|
||||
result, err := parser.ParseWithVersion(registry, "LoginReply", clientVersion)
|
||||
```
|
||||
|
||||
## Real example
|
||||
|
||||
```go
|
||||
type CharacterData struct {
|
||||
// Basic stuff
|
||||
CharID uint32 `eq2:"int32"`
|
||||
Name common.EQ2String16 `eq2:"string16"`
|
||||
Level uint8 `eq2:"int8"`
|
||||
|
||||
// Guild info (only if they're in a guild)
|
||||
HasGuild uint8 `eq2:"int8"`
|
||||
GuildID uint32 `eq2:"int32,ifvariableset=HasGuild"`
|
||||
|
||||
// Variable number of items
|
||||
ItemCount uint16 `eq2:"int16"`
|
||||
Items []InventoryItem `eq2:"array,arraysize=ItemCount"`
|
||||
|
||||
// Nested stuff
|
||||
Stats PlayerStats `eq2:"substruct"`
|
||||
}
|
||||
|
||||
type InventoryItem struct {
|
||||
ItemID uint32 `eq2:"int32"`
|
||||
Quantity uint16 `eq2:"int16"`
|
||||
Color common.EQ2Color `eq2:"color"`
|
||||
}
|
||||
|
||||
type PlayerStats struct {
|
||||
Health uint32 `eq2:"int32"`
|
||||
Mana uint32 `eq2:"int32"`
|
||||
Stamina uint32 `eq2:"int32"`
|
||||
}
|
||||
```
|
||||
|
||||
## Converting from XML
|
||||
|
||||
If you've got EQ2's XML packet definitions, the conversion is pretty straightforward:
|
||||
|
||||
| XML | Go Tag |
|
||||
|-----|--------|
|
||||
| `Type="int32"` | `eq2:"int32"` |
|
||||
| `ArraySizeVariable="count"` | `arraysize=Count` |
|
||||
| `IfVariableSet="flag"` | `ifvariableset=Flag` |
|
||||
| `Size="5"` | `len=5` |
|
@ -62,7 +62,7 @@ func (p *Parser) readSubstruct(field reflect.Value, length int) error {
|
||||
elemType := field.Type().Elem()
|
||||
slice := reflect.MakeSlice(field.Type(), length, length)
|
||||
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
elem := slice.Index(i)
|
||||
if err := p.parseStructElement(elem, elemType); err != nil {
|
||||
return fmt.Errorf("substruct element %d: %w", i, err)
|
||||
@ -95,7 +95,7 @@ func (p *Parser) parseStructElement(elem reflect.Value, elemType reflect.Type) e
|
||||
p.fieldCache = make(map[string]any)
|
||||
p.structStack = append(p.structStack, elem)
|
||||
|
||||
for i := 0; i < elem.NumField(); i++ {
|
||||
for i := range elem.NumField() {
|
||||
field := elem.Field(i)
|
||||
fieldType := elemType.Field(i)
|
||||
|
||||
|
@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// evaluateAllConditions checks all conditional logic
|
||||
func (p *Parser) evaluateAllConditions(fieldTag *FieldTag, field reflect.Value) bool {
|
||||
func (p *Parser) evaluateAllConditions(fieldTag *FieldTag, _ reflect.Value) bool {
|
||||
if fieldTag.Condition != nil && !p.evaluateCondition(fieldTag.Condition) {
|
||||
return false
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// readInt8 handles uint8/int8 reading
|
||||
func (p *Parser) readInt8(field reflect.Value, length int) error {
|
||||
if length == 1 {
|
||||
val, err := p.readUint8()
|
||||
@ -17,7 +16,7 @@ func (p *Parser) readInt8(field reflect.Value, length int) error {
|
||||
}
|
||||
|
||||
slice := make([]uint8, length)
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
val, err := p.readUint8()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -39,7 +38,7 @@ func (p *Parser) readInt16(field reflect.Value, length int) error {
|
||||
}
|
||||
|
||||
slice := make([]uint16, length)
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
val, err := p.readUint16()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -67,7 +66,7 @@ func (p *Parser) readInt32(field reflect.Value, length int) error {
|
||||
}
|
||||
|
||||
slice := make([]uint32, length)
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
val, err := p.readUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -89,7 +88,7 @@ func (p *Parser) readInt64(field reflect.Value, length int) error {
|
||||
}
|
||||
|
||||
slice := make([]uint64, length)
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
val, err := p.readUint64()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -111,7 +110,7 @@ func (p *Parser) readSInt8(field reflect.Value, length int) error {
|
||||
}
|
||||
|
||||
slice := make([]int8, length)
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
val, err := p.readUint8()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -133,7 +132,7 @@ func (p *Parser) readSInt16(field reflect.Value, length int) error {
|
||||
}
|
||||
|
||||
slice := make([]int16, length)
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
val, err := p.readUint16()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -155,7 +154,7 @@ func (p *Parser) readSInt32(field reflect.Value, length int) error {
|
||||
}
|
||||
|
||||
slice := make([]int32, length)
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
val, err := p.readUint32()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -224,7 +223,7 @@ func (p *Parser) readFloat(field reflect.Value, length int) error {
|
||||
}
|
||||
|
||||
slice := make([]float32, length)
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
val, err := p.readFloat32()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -246,7 +245,7 @@ func (p *Parser) readDouble(field reflect.Value, length int) error {
|
||||
}
|
||||
|
||||
slice := make([]float64, length)
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
val, err := p.readFloat64()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -338,7 +337,7 @@ func (p *Parser) readColor(field reflect.Value, length int) error {
|
||||
}
|
||||
|
||||
slice := make([]common.EQ2Color, length)
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
var err error
|
||||
slice[i].Red, err = p.readUint8()
|
||||
if err != nil {
|
||||
@ -397,7 +396,7 @@ func (p *Parser) readEquipment(field reflect.Value, length int) error {
|
||||
}
|
||||
|
||||
slice := make([]common.EQ2EquipmentItem, length)
|
||||
for i := 0; i < length; i++ {
|
||||
for i := range length {
|
||||
var err error
|
||||
slice[i].Type, err = p.readUint16()
|
||||
if err != nil {
|
||||
|
Loading…
x
Reference in New Issue
Block a user