package cps import ( "testing" ) const testCSS = ` CreateCharacter { .v1 { account_id: int32; server_id: int32; name: string16; race: int8; gender: int8; deity: int8; class: int8; level: int8; starting_zone: int8; unknown1: int8[2]; race_file: string16; skin_color: float[3]; eye_color: float[3]; hair_color1: float[3]; hair_color2: float[3]; hair_highlight: float[3]; unknown2: int8[26]; hair_file: string16; hair_type_color: float[3]; hair_type_highlight_color: float[3]; face_file: string16; hair_face_color: float[3]; hair_face_highlight_color: float[3]; chest_file: string16; shirt_color: float[3]; unknown_chest_color: float[3]; legs_file: string16; pants_color: float[3]; unknown_legs_color: float[3]; unknown9: float[3]; eyes2: float[3]; ears: float[3]; eye_brows: float[3]; cheeks: float[3]; lips: float[3]; chin: float[3]; nose: float[3]; body_size: float; body_age: float; } .v373 { unknown0: int32 @0; /* Insert at beginning */ } .v562 { unknown0: int8; /* Override type from v373 */ unknown1: int32 @1; /* New field after unknown0 */ unknown3: int8 @3; /* Insert at position 3 */ /* Override color types */ skin_color: color; skin_color2: color @after(skin_color); eye_color: color; hair_color1: color; hair_color2: color; hair_highlight: color; hair_type_color: color; hair_type_highlight_color: color; hair_face_color: color; hair_face_highlight_color: color; shirt_color: color; unknown_chest_color: color; pants_color: color; unknown_legs_color: color; unknown9: color; /* Add wing support */ wing_file: string16 @after(hair_face_highlight_color); wing_color1: color; wing_color2: color; /* Add SOGA system */ soga_version: int8; soga_race_file: string16; soga_skin_color: color; soga_eye_color: color; soga_hair_color1: color; soga_hair_color2: color; soga_hair_highlight: color; soga_unknown11: int8[26]; soga_hair_file: string16; soga_hair_type_color: color; soga_hair_type_highlight_color: color; soga_face_file: string16; soga_hair_face_color: color; soga_hair_face_highlight_color: color; soga_wing_file: string16; soga_wing_color1: color; soga_wing_color2: color; soga_chest_file: string16; soga_shirt_color: color; soga_unknown_chest_color: color; soga_legs_file: string16; soga_pants_color: color; soga_unknown_legs_color: color; soga_unknown12: color; soga_eyes2: float[3]; soga_ears: float[3]; soga_eye_brows: float[3]; soga_cheeks: float[3]; soga_lips: float[3]; soga_chin: float[3]; soga_nose: float[3]; soga_body_size: float; soga_body_age: float; } .v57080 { unknown10: int16 @after(starting_zone); unknown_skin_color2: color @after(eye_color); /* Remove skin_color2 positioning */ skin_color2: none; } } BadLanguageFilter { .v1 { num_words: int16; words_array: string16[]; } }` func TestParser(t *testing.T) { parser := NewParser(testCSS) packets, err := parser.Parse() if err != nil { t.Fatalf("Parse failed: %v", err) } // Verify CreateCharacter packet exists createChar, exists := packets["CreateCharacter"] if !exists { t.Fatal("CreateCharacter packet not found") } // Verify versions expectedVersions := []int{1, 373, 562, 57080} for _, version := range expectedVersions { if _, exists := createChar.Versions[version]; !exists { t.Errorf("Version %d not found", version) } } // Verify v1 has expected fields v1 := createChar.Versions[1] expectedV1Fields := []string{"account_id", "name", "race", "skin_color"} for _, fieldName := range expectedV1Fields { if _, exists := v1.Fields[fieldName]; !exists { t.Errorf("v1 missing field: %s", fieldName) } } // Verify field types if v1.Fields["account_id"].Type != TypeInt32 { t.Error("account_id should be int32") } if v1.Fields["name"].Type != TypeString16 { t.Error("name should be string16") } if v1.Fields["skin_color"].Type != TypeFloat32 || v1.Fields["skin_color"].Size != 3 { t.Error("skin_color should be float[3]") } // Verify v373 override v373 := createChar.Versions[373] if v373.Fields["unknown0"].Type != TypeInt32 || v373.Fields["unknown0"].Position != 0 { t.Error("v373 unknown0 should be int32 @0") } // Verify v562 type override v562 := createChar.Versions[562] if v562.Fields["unknown0"].Type != TypeInt8 { t.Error("v562 should override unknown0 to int8") } if v562.Fields["skin_color"].Type != TypeColor { t.Error("v562 should override skin_color to color") } // Verify positioning if v562.Fields["unknown1"].Position != 1 { t.Error("unknown1 should have position 1") } if v562.Fields["skin_color2"].After != "skin_color" { t.Error("skin_color2 should be after skin_color") } // Verify removal v57080 := createChar.Versions[57080] if !v57080.Fields["skin_color2"].Remove { t.Error("skin_color2 should be removed in v57080") } } func TestCompiler(t *testing.T) { manager := MustLoadCSS(testCSS) // Test v1 compilation packet, err := manager.GetPacket("CreateCharacter", 1) if err != nil { t.Fatalf("Failed to compile v1: %v", err) } if packet.Name != "CreateCharacter" || packet.Version != 1 { t.Error("Incorrect packet name or version") } // Verify field order and types if len(packet.Fields) == 0 { t.Fatal("No fields in compiled packet") } // First field should be account_id if packet.Fields[0].Name != "account_id" || packet.Fields[0].Type != TypeInt32 { t.Error("First field should be account_id:int32") } // Test v373 compilation (should inherit v1 + unknown0 at beginning) packet373, err := manager.GetPacket("CreateCharacter", 373) if err != nil { t.Fatalf("Failed to compile v373: %v", err) } // Should have more fields than v1 if len(packet373.Fields) <= len(packet.Fields) { t.Error("v373 should have more fields than v1") } // First field should be unknown0 if packet373.Fields[0].Name != "unknown0" || packet373.Fields[0].Type != TypeInt32 { t.Error("v373 first field should be unknown0:int32") } // Test v562 compilation (type overrides) packet562, err := manager.GetPacket("CreateCharacter", 562) if err != nil { t.Fatalf("Failed to compile v562: %v", err) } // Find skin_color field and verify it's now color type var skinColorField *CompiledField for _, field := range packet562.Fields { if field.Name == "skin_color" { skinColorField = field break } } if skinColorField == nil || skinColorField.Type != TypeColor { t.Error("v562 skin_color should be color type") } // Verify unknown0 type override if packet562.Fields[0].Type != TypeInt8 { t.Error("v562 unknown0 should be int8 (overridden from int32)") } // Test field removal in v57080 packet57080, err := manager.GetPacket("CreateCharacter", 57080) if err != nil { t.Fatalf("Failed to compile v57080: %v", err) } // skin_color2 should not exist for _, field := range packet57080.Fields { if field.Name == "skin_color2" { t.Error("skin_color2 should be removed in v57080") } } } func TestVersionSelection(t *testing.T) { manager := MustLoadCSS(testCSS) // Test exact version match packet, err := manager.GetPacket("CreateCharacter", 562) if err != nil { t.Fatalf("Failed to get v562: %v", err) } if packet.Version != 562 { t.Error("Should get exact version match") } // Test version fallback (should get highest <= requested) packet, err = manager.GetPacket("CreateCharacter", 500) if err != nil { t.Fatalf("Failed to get v500 fallback: %v", err) } if packet.Version != 373 { t.Error("Should fallback to v373 for client v500") } // Test future version (should get highest available) packet, err = manager.GetPacket("CreateCharacter", 99999) if err != nil { t.Fatalf("Failed to get future version: %v", err) } if packet.Version != 57080 { t.Error("Should get highest version for future client") } // Test too old client _, err = manager.GetPacket("CreateCharacter", 0) if err == nil { t.Error("Should fail for client version 0") } } func TestSerialization(t *testing.T) { manager := MustLoadCSS(testCSS) serializer := NewSerializer(manager) // Test v1 serialization data := map[string]any{ "account_id": int32(12345), "server_id": int32(1), "name": "TestChar", "race": int8(1), "gender": int8(1), "deity": int8(0), "class": int8(1), "level": int8(1), "skin_color": []float32{1.0, 0.8, 0.6}, } bytes, err := serializer.Serialize("CreateCharacter", 1, data) if err != nil { t.Fatalf("Serialization failed: %v", err) } if len(bytes) == 0 { t.Fatal("Serialized data is empty") } // Test deserialization parsed, err := serializer.Deserialize("CreateCharacter", 1, bytes) if err != nil { t.Fatalf("Deserialization failed: %v", err) } // Verify key fields if parsed["account_id"].(int32) != 12345 { t.Error("account_id not preserved") } if parsed["name"].(string) != "TestChar" { t.Error("name not preserved") } if parsed["race"].(int8) != 1 { t.Error("race not preserved") } // Verify float array skinColor := parsed["skin_color"].([]float32) if len(skinColor) != 3 || skinColor[0] != 1.0 { t.Error("skin_color array not preserved") } } func TestColorSerialization(t *testing.T) { manager := MustLoadCSS(testCSS) serializer := NewSerializer(manager) // Test v562 with color types data := map[string]any{ "account_id": int32(12345), "name": "TestChar", "skin_color": uint32(0xFF0000FF), // Red color "eye_color": uint32(0x0000FFFF), // Blue color "wing_color1": uint32(0x00FF00FF), // Green color } bytes, err := serializer.Serialize("CreateCharacter", 562, data) if err != nil { t.Fatalf("v562 serialization failed: %v", err) } parsed, err := serializer.Deserialize("CreateCharacter", 562, bytes) if err != nil { t.Fatalf("v562 deserialization failed: %v", err) } // Verify colors preserved if parsed["skin_color"].(uint32) != 0xFF0000FF { t.Error("skin_color not preserved") } if parsed["eye_color"].(uint32) != 0x0000FFFF { t.Error("eye_color not preserved") } } func TestBadLanguageFilter(t *testing.T) { manager := MustLoadCSS(testCSS) packet, err := manager.GetPacket("BadLanguageFilter", 1) if err != nil { t.Fatalf("Failed to get BadLanguageFilter: %v", err) } if len(packet.Fields) != 2 { t.Error("BadLanguageFilter should have 2 fields") } // Verify dynamic array wordsField := packet.Fields[1] if wordsField.Name != "words_array" || !wordsField.Dynamic { t.Error("words_array should be dynamic") } } func TestCaching(t *testing.T) { manager := MustLoadCSS(testCSS) // Get same packet twice packet1, err := manager.GetPacket("CreateCharacter", 562) if err != nil { t.Fatalf("First get failed: %v", err) } packet2, err := manager.GetPacket("CreateCharacter", 562) if err != nil { t.Fatalf("Second get failed: %v", err) } // Should be same pointer (cached) if packet1 != packet2 { t.Error("Packets should be cached and identical") } // Clear cache and verify new instance manager.ClearCache() packet3, err := manager.GetPacket("CreateCharacter", 562) if err != nil { t.Fatalf("Third get failed: %v", err) } if packet1 == packet3 { t.Error("After cache clear, should get new instance") } } func TestErrorCases(t *testing.T) { // Test invalid CSS invalidCSS := `CreateCharacter { .v1 { invalid syntax } }` manager := NewManager() err := manager.LoadCSS(invalidCSS) if err == nil { t.Error("Should fail on invalid CSS") } // Test missing packet manager = MustLoadCSS(testCSS) _, err = manager.GetPacket("NonExistent", 1) if err == nil { t.Error("Should fail on missing packet") } // Test serialization with wrong data types serializer := NewSerializer(manager) badData := map[string]any{ "account_id": "not_a_number", // Wrong type } _, err = serializer.Serialize("CreateCharacter", 1, badData) if err == nil { t.Error("Should fail on wrong data type") } }