425 lines
11 KiB
Go
425 lines
11 KiB
Go
package parser
|
|
|
|
import (
|
|
"eq2emu/internal/common"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
type BasicPacket struct {
|
|
Channel uint8 `eq2:"int8"`
|
|
Count uint16 `eq2:"int16"`
|
|
MessageType uint8 `eq2:"int8,if=Channel==1"`
|
|
}
|
|
|
|
type ArrayPacket struct {
|
|
ItemCount uint16 `eq2:"int16"`
|
|
Items []ItemDescription `eq2:"array,arraysize=ItemCount"`
|
|
}
|
|
|
|
type ItemDescription struct {
|
|
InfoHeader WS_ExamineInfoHeader `eq2:"substruct"`
|
|
Info BaseItemDescription `eq2:"substruct"`
|
|
ItemType uint8 `eq2:"int8"`
|
|
}
|
|
|
|
type WS_ExamineInfoHeader struct {
|
|
Unknown1 uint32 `eq2:"int32"`
|
|
Unknown2 uint16 `eq2:"int16"`
|
|
}
|
|
|
|
type BaseItemDescription struct {
|
|
Name common.EQ2String16 `eq2:"string16"`
|
|
Category common.EQ2String8 `eq2:"string8"`
|
|
}
|
|
|
|
type ConditionalPacket struct {
|
|
HasRewards uint8 `eq2:"int8"`
|
|
RewardData *RewardInfo `eq2:"substruct,ifvariableset=HasRewards"`
|
|
CompleteFlag uint8 `eq2:"int8"`
|
|
ClassicSound uint8 `eq2:"int8,ifvariableset=CompleteFlag"`
|
|
}
|
|
|
|
type RewardInfo struct {
|
|
RewardType uint8 `eq2:"int8"`
|
|
Amount uint32 `eq2:"int32"`
|
|
}
|
|
|
|
type OversizedPacket struct {
|
|
LargeDataCount uint16 `eq2:"int16"`
|
|
LargeData []byte `eq2:"char,len=LargeDataCount,maxsize=10,skipoversized"`
|
|
}
|
|
|
|
type TypeSwitchPacket struct {
|
|
StatType uint8 `eq2:"int8"`
|
|
StatValue any `eq2:"int32,type2=float,type2criteria=StatType!=6"`
|
|
}
|
|
|
|
type FlagPacket struct {
|
|
Equipment []common.EQ2EquipmentItem `eq2:"equipment,len=2,ifflag=has_equipment"`
|
|
Colors []common.EQ2Color `eq2:"color,len=3,ifflagnotset=no_colors"`
|
|
}
|
|
|
|
type ComplexPacket struct {
|
|
GroupCount uint8 `eq2:"int8"`
|
|
Groups []Group `eq2:"array,arraysize=GroupCount"`
|
|
}
|
|
|
|
type Group struct {
|
|
MemberCount uint16 `eq2:"int16"`
|
|
Members []Member `eq2:"array,arraysize=MemberCount"`
|
|
}
|
|
|
|
type Member struct {
|
|
Name common.EQ2String16 `eq2:"string16"`
|
|
Level uint8 `eq2:"int8"`
|
|
Class uint8 `eq2:"int8"`
|
|
}
|
|
|
|
// Version-specific structs
|
|
type PacketV1 struct {
|
|
Field1 uint8 `eq2:"int8"`
|
|
Field2 uint16 `eq2:"int16"`
|
|
}
|
|
|
|
type PacketV2 struct {
|
|
Field1 uint8 `eq2:"int8"`
|
|
Field2 uint16 `eq2:"int16"`
|
|
NewField uint32 `eq2:"int32"`
|
|
}
|
|
|
|
func TestBasicParsing(t *testing.T) {
|
|
// Basic packet: Channel=1, Count=100, MessageType=5
|
|
data := []byte{0x01, 0x64, 0x00, 0x05}
|
|
parser := NewParser(data)
|
|
|
|
var packet BasicPacket
|
|
err := parser.ParseStruct(&packet)
|
|
if err != nil {
|
|
t.Fatalf("Parse error: %v", err)
|
|
}
|
|
|
|
if packet.Channel != 1 {
|
|
t.Errorf("Expected Channel=1, got %d", packet.Channel)
|
|
}
|
|
if packet.Count != 100 {
|
|
t.Errorf("Expected Count=100, got %d", packet.Count)
|
|
}
|
|
if packet.MessageType != 5 {
|
|
t.Errorf("Expected MessageType=5, got %d", packet.MessageType)
|
|
}
|
|
}
|
|
|
|
func TestConditionalSkip(t *testing.T) {
|
|
// Channel=2 (not 1), so MessageType should be skipped
|
|
data := []byte{0x02, 0x64, 0x00}
|
|
parser := NewParser(data)
|
|
|
|
var packet BasicPacket
|
|
err := parser.ParseStruct(&packet)
|
|
if err != nil {
|
|
t.Fatalf("Parse error: %v", err)
|
|
}
|
|
|
|
if packet.Channel != 2 {
|
|
t.Errorf("Expected Channel=2, got %d", packet.Channel)
|
|
}
|
|
if packet.MessageType != 0 {
|
|
t.Errorf("Expected MessageType=0 (skipped), got %d", packet.MessageType)
|
|
}
|
|
}
|
|
|
|
func TestArrayParsing(t *testing.T) {
|
|
// ItemCount=2, then 2 items
|
|
data := []byte{
|
|
0x02, 0x00, // ItemCount = 2
|
|
// Item 1
|
|
0x01, 0x00, 0x00, 0x00, // InfoHeader.Unknown1 = 1
|
|
0x02, 0x00, // InfoHeader.Unknown2 = 2
|
|
0x04, 0x00, 't', 'e', 's', 't', // Name: "test"
|
|
0x03, 'c', 'a', 't', // Category: "cat"
|
|
0x05, // ItemType = 5
|
|
// Item 2
|
|
0x03, 0x00, 0x00, 0x00, // InfoHeader.Unknown1 = 3
|
|
0x04, 0x00, // InfoHeader.Unknown2 = 4
|
|
0x05, 0x00, 'i', 't', 'e', 'm', '2', // Name: "item2"
|
|
0x04, 't', 'y', 'p', 'e', // Category: "type"
|
|
0x06, // ItemType = 6
|
|
}
|
|
|
|
parser := NewParser(data)
|
|
var packet ArrayPacket
|
|
err := parser.ParseStruct(&packet)
|
|
if err != nil {
|
|
t.Fatalf("Parse error: %v", err)
|
|
}
|
|
|
|
if packet.ItemCount != 2 {
|
|
t.Errorf("Expected ItemCount=2, got %d", packet.ItemCount)
|
|
}
|
|
if len(packet.Items) != 2 {
|
|
t.Errorf("Expected 2 items, got %d", len(packet.Items))
|
|
}
|
|
|
|
if packet.Items[0].Info.Name.Data != "test" {
|
|
t.Errorf("Expected first item name 'test', got '%s'", packet.Items[0].Info.Name.Data)
|
|
}
|
|
if packet.Items[1].ItemType != 6 {
|
|
t.Errorf("Expected second item type 6, got %d", packet.Items[1].ItemType)
|
|
}
|
|
}
|
|
|
|
func TestConditionalFields(t *testing.T) {
|
|
// HasRewards=1, RewardData present, CompleteFlag=1, ClassicSound present
|
|
data := []byte{
|
|
0x01, // HasRewards = 1
|
|
0x02, // RewardType = 2
|
|
0x64, 0x00, 0x00, 0x00, // Amount = 100
|
|
0x01, // CompleteFlag = 1
|
|
0x05, // ClassicSound = 5
|
|
}
|
|
|
|
parser := NewParser(data)
|
|
var packet ConditionalPacket
|
|
err := parser.ParseStruct(&packet)
|
|
if err != nil {
|
|
t.Fatalf("Parse error: %v", err)
|
|
}
|
|
|
|
if packet.HasRewards != 1 {
|
|
t.Errorf("Expected HasRewards=1, got %d", packet.HasRewards)
|
|
}
|
|
if packet.RewardData == nil {
|
|
t.Error("Expected RewardData to be present")
|
|
} else {
|
|
if packet.RewardData.RewardType != 2 {
|
|
t.Errorf("Expected RewardType=2, got %d", packet.RewardData.RewardType)
|
|
}
|
|
if packet.RewardData.Amount != 100 {
|
|
t.Errorf("Expected Amount=100, got %d", packet.RewardData.Amount)
|
|
}
|
|
}
|
|
if packet.ClassicSound != 5 {
|
|
t.Errorf("Expected ClassicSound=5, got %d", packet.ClassicSound)
|
|
}
|
|
}
|
|
|
|
func TestOversizedHandling(t *testing.T) {
|
|
// LargeDataCount=15 (exceeds maxsize=10), should be truncated
|
|
data := []byte{
|
|
0x0F, 0x00, // LargeDataCount = 15
|
|
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A,
|
|
0x0B, 0x0C, 0x0D, 0x0E, 0x0F, // 15 bytes of data
|
|
}
|
|
|
|
parser := NewParser(data)
|
|
var packet OversizedPacket
|
|
err := parser.ParseStruct(&packet)
|
|
if err != nil {
|
|
t.Fatalf("Parse error: %v", err)
|
|
}
|
|
|
|
if packet.LargeDataCount != 15 {
|
|
t.Errorf("Expected LargeDataCount=15, got %d", packet.LargeDataCount)
|
|
}
|
|
if len(packet.LargeData) != 10 {
|
|
t.Errorf("Expected LargeData length=10 (truncated), got %d", len(packet.LargeData))
|
|
}
|
|
}
|
|
|
|
func TestTypeSwitching(t *testing.T) {
|
|
// StatType=3 (not 6), so StatValue should be parsed as float
|
|
data := []byte{
|
|
0x03, // StatType = 3
|
|
0x00, 0x00, 0x80, 0x3F, // float32: 1.0
|
|
}
|
|
|
|
parser := NewParser(data)
|
|
var packet TypeSwitchPacket
|
|
err := parser.ParseStruct(&packet)
|
|
if err != nil {
|
|
t.Fatalf("Parse error: %v", err)
|
|
}
|
|
|
|
if packet.StatType != 3 {
|
|
t.Errorf("Expected StatType=3, got %d", packet.StatType)
|
|
}
|
|
|
|
// Should be parsed as float32 since StatType != 6
|
|
floatVal, ok := packet.StatValue.(float32)
|
|
if !ok {
|
|
t.Errorf("Expected StatValue to be float32, got %T", packet.StatValue)
|
|
} else if floatVal != 1.0 {
|
|
t.Errorf("Expected StatValue=1.0, got %f", floatVal)
|
|
}
|
|
}
|
|
|
|
func TestFlagBasedConditionals(t *testing.T) {
|
|
// Equipment data (2 items)
|
|
data := []byte{
|
|
// Equipment item 1
|
|
0x01, 0x00, // Type = 1
|
|
0xFF, 0x00, 0x00, // Color: Red=255, Green=0, Blue=0
|
|
0x00, 0xFF, 0x00, // Highlight: Red=0, Green=255, Blue=0
|
|
// Equipment item 2
|
|
0x02, 0x00, // Type = 2
|
|
0x00, 0x00, 0xFF, // Color: Red=0, Green=0, Blue=255
|
|
0xFF, 0xFF, 0x00, // Highlight: Red=255, Green=255, Blue=0
|
|
// Colors (3 items)
|
|
0x80, 0x80, 0x80, // Color 1: Gray
|
|
0x40, 0x40, 0x40, // Color 2: Dark gray
|
|
0xC0, 0xC0, 0xC0, // Color 3: Light gray
|
|
}
|
|
|
|
parser := NewParser(data)
|
|
parser.SetFlag("has_equipment", true)
|
|
// no_colors flag is not set, so colors should be parsed
|
|
|
|
var packet FlagPacket
|
|
err := parser.ParseStruct(&packet)
|
|
if err != nil {
|
|
t.Fatalf("Parse error: %v", err)
|
|
}
|
|
|
|
if len(packet.Equipment) != 2 {
|
|
t.Errorf("Expected 2 equipment items, got %d", len(packet.Equipment))
|
|
}
|
|
if len(packet.Colors) != 3 {
|
|
t.Errorf("Expected 3 colors, got %d", len(packet.Colors))
|
|
}
|
|
|
|
if packet.Equipment[0].Type != 1 {
|
|
t.Errorf("Expected first equipment type 1, got %d", packet.Equipment[0].Type)
|
|
}
|
|
if packet.Colors[0].Red != 0x80 {
|
|
t.Errorf("Expected first color red=128, got %d", packet.Colors[0].Red)
|
|
}
|
|
}
|
|
|
|
func TestComplexNestedArrays(t *testing.T) {
|
|
// GroupCount=1, Group has MemberCount=2
|
|
data := []byte{
|
|
0x01, // GroupCount = 1
|
|
0x02, 0x00, // MemberCount = 2
|
|
// Member 1
|
|
0x05, 0x00, 'A', 'l', 'i', 'c', 'e', // Name: "Alice"
|
|
0x0A, // Level = 10
|
|
0x01, // Class = 1
|
|
// Member 2
|
|
0x03, 0x00, 'B', 'o', 'b', // Name: "Bob"
|
|
0x0F, // Level = 15
|
|
0x02, // Class = 2
|
|
}
|
|
|
|
parser := NewParser(data)
|
|
var packet ComplexPacket
|
|
err := parser.ParseStruct(&packet)
|
|
if err != nil {
|
|
t.Fatalf("Parse error: %v", err)
|
|
}
|
|
|
|
if packet.GroupCount != 1 {
|
|
t.Errorf("Expected GroupCount=1, got %d", packet.GroupCount)
|
|
}
|
|
if len(packet.Groups) != 1 {
|
|
t.Errorf("Expected 1 group, got %d", len(packet.Groups))
|
|
}
|
|
if len(packet.Groups[0].Members) != 2 {
|
|
t.Errorf("Expected 2 members, got %d", len(packet.Groups[0].Members))
|
|
}
|
|
|
|
if packet.Groups[0].Members[0].Name.Data != "Alice" {
|
|
t.Errorf("Expected first member 'Alice', got '%s'", packet.Groups[0].Members[0].Name.Data)
|
|
}
|
|
if packet.Groups[0].Members[1].Level != 15 {
|
|
t.Errorf("Expected second member level 15, got %d", packet.Groups[0].Members[1].Level)
|
|
}
|
|
}
|
|
|
|
func TestVersionRegistry(t *testing.T) {
|
|
registry := NewVersionRegistry()
|
|
|
|
// Register different versions
|
|
registry.RegisterStruct("TestPacket", "1.0", reflect.TypeOf(PacketV1{}))
|
|
registry.RegisterStruct("TestPacket", "2.0", reflect.TypeOf(PacketV2{}))
|
|
|
|
// Test version 1.0
|
|
data1 := []byte{0x01, 0x64, 0x00} // Field1=1, Field2=100
|
|
parser1 := NewParser(data1)
|
|
|
|
result1, err := parser1.ParseWithVersion(registry, "TestPacket", "1.0")
|
|
if err != nil {
|
|
t.Fatalf("Parse v1.0 error: %v", err)
|
|
}
|
|
|
|
v1Packet, ok := result1.(PacketV1)
|
|
if !ok {
|
|
t.Fatal("Expected PacketV1 type")
|
|
}
|
|
if v1Packet.Field1 != 1 || v1Packet.Field2 != 100 {
|
|
t.Errorf("V1 packet values incorrect: Field1=%d, Field2=%d", v1Packet.Field1, v1Packet.Field2)
|
|
}
|
|
|
|
// Test version 2.0
|
|
data2 := []byte{0x02, 0xC8, 0x00, 0x90, 0x01, 0x00, 0x00} // Field1=2, Field2=200, NewField=400
|
|
parser2 := NewParser(data2)
|
|
|
|
result2, err := parser2.ParseWithVersion(registry, "TestPacket", "2.0")
|
|
if err != nil {
|
|
t.Fatalf("Parse v2.0 error: %v", err)
|
|
}
|
|
|
|
v2Packet, ok := result2.(PacketV2)
|
|
if !ok {
|
|
t.Fatal("Expected PacketV2 type")
|
|
}
|
|
if v2Packet.Field1 != 2 || v2Packet.Field2 != 200 || v2Packet.NewField != 400 {
|
|
t.Errorf("V2 packet values incorrect: Field1=%d, Field2=%d, NewField=%d",
|
|
v2Packet.Field1, v2Packet.Field2, v2Packet.NewField)
|
|
}
|
|
}
|
|
|
|
func TestVersionFallback(t *testing.T) {
|
|
registry := NewVersionRegistry()
|
|
registry.RegisterStruct("TestPacket", "1.0", reflect.TypeOf(PacketV1{}))
|
|
registry.RegisterStruct("TestPacket", "2.0", reflect.TypeOf(PacketV2{}))
|
|
|
|
// Request version 1.5 (doesn't exist), should fall back to 1.0
|
|
data := []byte{0x01, 0x64, 0x00}
|
|
parser := NewParser(data)
|
|
|
|
result, err := parser.ParseWithVersion(registry, "TestPacket", "1.5")
|
|
if err != nil {
|
|
t.Fatalf("Parse with fallback error: %v", err)
|
|
}
|
|
|
|
// Should get v1.0 (nearest lower version)
|
|
_, ok := result.(PacketV1)
|
|
if !ok {
|
|
t.Error("Expected fallback to PacketV1")
|
|
}
|
|
}
|
|
|
|
func TestParserPosition(t *testing.T) {
|
|
data := []byte{0x01, 0x02, 0x03, 0x04}
|
|
parser := NewParser(data)
|
|
|
|
if parser.Offset() != 0 {
|
|
t.Errorf("Initial offset should be 0, got %d", parser.Offset())
|
|
}
|
|
|
|
parser.readUint8() // Read one byte
|
|
if parser.Offset() != 1 {
|
|
t.Errorf("After reading 1 byte, offset should be 1, got %d", parser.Offset())
|
|
}
|
|
|
|
if parser.Remaining() != 3 {
|
|
t.Errorf("After reading 1 byte, remaining should be 3, got %d", parser.Remaining())
|
|
}
|
|
|
|
parser.SetOffset(2)
|
|
if parser.Offset() != 2 {
|
|
t.Errorf("After SetOffset(2), offset should be 2, got %d", parser.Offset())
|
|
}
|
|
}
|