package ls_structs import ( "eq2emu/internal/packets/parser" "testing" ) func TestDeleteCharacterRequest(t *testing.T) { // CharID=12345, ServerID=1, Unknown=0, Name="TestChar" data := []byte{ 0x39, 0x30, 0x00, 0x00, // CharID = 12345 0x01, 0x00, 0x00, 0x00, // ServerID = 1 0x00, 0x00, 0x00, 0x00, // Unknown = 0 0x08, 0x00, 'T', 'e', 's', 't', 'C', 'h', 'a', 'r', // Name = "TestChar" } parser := parser.NewParser(data) var packet DeleteCharacterRequest err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.CharID != 12345 { t.Errorf("Expected CharID=12345, got %d", packet.CharID) } if packet.ServerID != 1 { t.Errorf("Expected ServerID=1, got %d", packet.ServerID) } if packet.Name.Data != "TestChar" { t.Errorf("Expected Name='TestChar', got '%s'", packet.Name.Data) } } func TestDeleteCharacterResponse(t *testing.T) { // Response=1, ServerID=1, CharID=12345, AccountID=54321, Name="TestChar", MaxCharacters=8 data := []byte{ 0x01, // Response = 1 0x01, 0x00, 0x00, 0x00, // ServerID = 1 0x39, 0x30, 0x00, 0x00, // CharID = 12345 0x31, 0xD4, 0x00, 0x00, // AccountID = 54321 0x08, 0x00, 'T', 'e', 's', 't', 'C', 'h', 'a', 'r', // Name = "TestChar" 0x08, 0x00, 0x00, 0x00, // MaxCharacters = 8 } parser := parser.NewParser(data) var packet DeleteCharacterResponse err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.Response != 1 { t.Errorf("Expected Response=1, got %d", packet.Response) } if packet.CharID != 12345 { t.Errorf("Expected CharID=12345, got %d", packet.CharID) } if packet.AccountID != 54321 { t.Errorf("Expected AccountID=54321, got %d", packet.AccountID) } if packet.MaxCharacters != 8 { t.Errorf("Expected MaxCharacters=8, got %d", packet.MaxCharacters) } } func TestCreateCharacterReplyV1(t *testing.T) { // AccountID=54321, Response=0, Name="NewChar" data := []byte{ 0x31, 0xD4, 0x00, 0x00, // AccountID = 54321 0x00, // Response = 0 0x07, 0x00, 'N', 'e', 'w', 'C', 'h', 'a', 'r', // Name = "NewChar" } parser := parser.NewParser(data) var packet CreateCharacterReplyV1 err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.AccountID != 54321 { t.Errorf("Expected AccountID=54321, got %d", packet.AccountID) } if packet.Response != 0 { t.Errorf("Expected Response=0, got %d", packet.Response) } if packet.Name.Data != "NewChar" { t.Errorf("Expected Name='NewChar', got '%s'", packet.Name.Data) } } func TestCreateCharacterReplyV1189(t *testing.T) { // AccountID=54321, Unknown=100, Response=0, Name="NewChar" data := []byte{ 0x31, 0xD4, 0x00, 0x00, // AccountID = 54321 0x64, 0x00, 0x00, 0x00, // Unknown = 100 0x00, // Response = 0 0x07, 0x00, 'N', 'e', 'w', 'C', 'h', 'a', 'r', // Name = "NewChar" } parser := parser.NewParser(data) var packet CreateCharacterReplyV1189 err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.AccountID != 54321 { t.Errorf("Expected AccountID=54321, got %d", packet.AccountID) } if packet.Unknown != 100 { t.Errorf("Expected Unknown=100, got %d", packet.Unknown) } if packet.Response != 0 { t.Errorf("Expected Response=0, got %d", packet.Response) } } func TestLoginRequestV1(t *testing.T) { data := []byte{ // SessionID = "session123" 0x0A, 0x00, 's', 'e', 's', 's', 'i', 'o', 'n', '1', '2', '3', // SessionRecycleToken = "token456" 0x08, 0x00, 't', 'o', 'k', 'e', 'n', '4', '5', '6', // Username = "testuser" 0x08, 0x00, 't', 'e', 's', 't', 'u', 's', 'e', 'r', // Password = "testpass" 0x08, 0x00, 't', 'e', 's', 't', 'p', 'a', 's', 's', 0x31, 0xD4, 0x00, 0x00, // AcctNum = 54321 0x39, 0x30, 0x00, 0x00, // PassCode = 12345 0xDC, 0x05, // Version = 1500 } parser := parser.NewParser(data) var packet LoginRequestV1 err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.SessionID.Data != "session123" { t.Errorf("Expected SessionID='session123', got '%s'", packet.SessionID.Data) } if packet.Username.Data != "testuser" { t.Errorf("Expected Username='testuser', got '%s'", packet.Username.Data) } if packet.AcctNum != 54321 { t.Errorf("Expected AcctNum=54321, got %d", packet.AcctNum) } if packet.Version != 1500 { t.Errorf("Expected Version=1500, got %d", packet.Version) } } func TestLoginByNumRequestV1(t *testing.T) { data := []byte{ 0x31, 0xD4, 0x00, 0x00, // AccountID = 54321 0x39, 0x30, 0x00, 0x00, // AccessCode = 12345 0xDC, 0x05, // Version = 1500 // Unknown2 = [1, 2, 3, 4, 5] 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, } parser := parser.NewParser(data) var packet LoginByNumRequestV1 err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.AccountID != 54321 { t.Errorf("Expected AccountID=54321, got %d", packet.AccountID) } if packet.AccessCode != 12345 { t.Errorf("Expected AccessCode=12345, got %d", packet.AccessCode) } if packet.Version != 1500 { t.Errorf("Expected Version=1500, got %d", packet.Version) } if len(packet.Unknown2) != 5 { t.Errorf("Expected Unknown2 length=5, got %d", len(packet.Unknown2)) } if packet.Unknown2[0] != 1 || packet.Unknown2[4] != 5 { t.Errorf("Expected Unknown2=[1,2,3,4,5], got %v", packet.Unknown2) } } func TestPlayRequestV1(t *testing.T) { data := []byte{ 0x39, 0x30, 0x00, 0x00, // CharID = 12345 0x08, 0x00, 'T', 'e', 's', 't', 'C', 'h', 'a', 'r', // Name = "TestChar" } parser := parser.NewParser(data) var packet PlayRequestV1 err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.CharID != 12345 { t.Errorf("Expected CharID=12345, got %d", packet.CharID) } if packet.Name.Data != "TestChar" { t.Errorf("Expected Name='TestChar', got '%s'", packet.Name.Data) } } func TestPlayResponseV1(t *testing.T) { data := []byte{ 0x01, // Response = 1 0x09, '1', '2', '7', '.', '0', '.', '0', '.', '1', // Server = "127.0.0.1" 0xC4, 0x09, // Port = 2500 0x31, 0xD4, 0x00, 0x00, // AccountID = 54321 0x39, 0x30, 0x00, 0x00, // AccessCode = 12345 } parser := parser.NewParser(data) var packet PlayResponseV1 err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.Response != 1 { t.Errorf("Expected Response=1, got %d", packet.Response) } if packet.Server.Data != "127.0.0.1" { t.Errorf("Expected Server='127.0.0.1', got '%s'", packet.Server.Data) } if packet.Port != 2500 { t.Errorf("Expected Port=2500, got %d", packet.Port) } if packet.AccountID != 54321 { t.Errorf("Expected AccountID=54321, got %d", packet.AccountID) } } func TestWorldListV1(t *testing.T) { data := []byte{ 0x02, // NumWorlds = 2 // World 1 0x01, 0x00, 0x00, 0x00, // ID = 1 0x07, 0x00, 'A', 'n', 't', 'o', 'n', 'i', 'a', // Name = "Antonia" 0x01, // Online = 1 0x00, // Locked = 0 0x00, // Unknown2 = 0 0x00, // Unknown3 = 0 0x32, // Load = 50 // World 2 0x02, 0x00, 0x00, 0x00, // ID = 2 0x08, 0x00, 'N', 'a', 'g', 'a', 'f', 'e', 'n', ' ', // Name = "Nagafen " 0x01, // Online = 1 0x01, // Locked = 1 0x00, // Unknown2 = 0 0x00, // Unknown3 = 0 0x64, // Load = 100 } parser := parser.NewParser(data) var packet WorldListV1 err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.NumWorlds != 2 { t.Errorf("Expected NumWorlds=2, got %d", packet.NumWorlds) } if len(packet.WorldList) != 2 { t.Errorf("Expected 2 worlds, got %d", len(packet.WorldList)) } if packet.WorldList[0].Name.Data != "Antonia" { t.Errorf("Expected first world 'Antonia', got '%s'", packet.WorldList[0].Name.Data) } if packet.WorldList[0].Online != 1 { t.Errorf("Expected first world online=1, got %d", packet.WorldList[0].Online) } if packet.WorldList[1].Locked != 1 { t.Errorf("Expected second world locked=1, got %d", packet.WorldList[1].Locked) } } func TestWorldListV373(t *testing.T) { data := []byte{ 0x01, // NumWorlds = 1 // World 1 0x01, 0x00, 0x00, 0x00, // ID = 1 0x07, 0x00, 'A', 'n', 't', 'o', 'n', 'i', 'a', // Name = "Antonia" 0x01, // Tag = 1 0x00, // Locked = 0 0x00, // Hidden = 0 0x00, // Unknown = 0 0x64, 0x00, // NumPlayers = 100 0x32, // Load = 50 0x01, // NumberOnlineFlag = 1 0xFF, 0xFF, 0xFF, 0xFF, // AllowedRaces = 0xFFFFFFFF } parser := parser.NewParser(data) var packet WorldListV373 err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.NumWorlds != 1 { t.Errorf("Expected NumWorlds=1, got %d", packet.NumWorlds) } if packet.WorldList[0].NumPlayers != 100 { t.Errorf("Expected NumPlayers=100, got %d", packet.WorldList[0].NumPlayers) } if packet.WorldList[0].AllowedRaces != 0xFFFFFFFF { t.Errorf("Expected AllowedRaces=0xFFFFFFFF, got %d", packet.WorldList[0].AllowedRaces) } } func TestLoginReplyMsgV1(t *testing.T) { data := []byte{ 0x00, // LoginResponse = 0 (success) 0x07, 0x00, 'A', 'n', 't', 'o', 'n', 'i', 'a', // WorldName = "Antonia" 0x00, // ParentalControlFlag = 0 0x00, 0x00, 0x00, 0x00, // ParentalControlTimer[0] = 0 0x00, 0x00, 0x00, 0x00, // ParentalControlTimer[1] = 0 0x00, 0x00, 0x00, 0x00, // ParentalControlNext = 0 0x31, 0xD4, 0x00, 0x00, // AccountID = 54321 } parser := parser.NewParser(data) var packet LoginReplyMsgV1 err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.LoginResponse != 0 { t.Errorf("Expected LoginResponse=0, got %d", packet.LoginResponse) } if packet.WorldName.Data != "Antonia" { t.Errorf("Expected WorldName='Antonia', got '%s'", packet.WorldName.Data) } if packet.AccountID != 54321 { t.Errorf("Expected AccountID=54321, got %d", packet.AccountID) } } func TestLoginReplyMsgV284WithClassItems(t *testing.T) { data := []byte{ 0x00, // LoginResponse = 0 0x07, 0x00, 'A', 'n', 't', 'o', 'n', 'i', 'a', // Unknown = "Antonia" 0x00, // ParentalControlFlag = 0 0x00, 0x00, 0x00, 0x00, // ParentalControlTimer = 0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Unknown2 (8 bytes) 0x31, 0xD4, 0x00, 0x00, // CacheSettingAccountID = 54321 0x04, 0x00, 't', 'e', 's', 't', // Unknown3 = "test" 0x00, // ResetAppearance = 0 0x00, // DoNotForceSoga = 0 0x00, 0x00, // Unknown5 = 0 0x00, // Unknown6 = 0 0x00, 0x00, 0x00, 0x00, // Unknown7 = 0 0x00, 0x00, // Unknown8 (2 bytes) 0x01, // Unknown10 = 1 (truthy, so class items will be parsed) 0x01, // NumClassItems = 1 // ClassItem 1 0x05, // ClassID = 5 0x01, // NumItems = 1 // StartingItem 1 0x64, 0x00, // ModelID = 100 0x01, // SlotID = 1 0x01, // UseColor = 1 0x01, // UseHighlightColor = 1 0xFF, 0x00, 0x00, // ModelColor = Red 0x00, 0xFF, 0x00, // ModelHighlightColor = Green 0x00, // UnknownArray2Size = 0 } parser := parser.NewParser(data) var packet LoginReplyMsgV284 err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.LoginResponse != 0 { t.Errorf("Expected LoginResponse=0, got %d", packet.LoginResponse) } if packet.CacheSettingAccountID != 54321 { t.Errorf("Expected CacheSettingAccountID=54321, got %d", packet.CacheSettingAccountID) } if packet.Unknown10 != 1 { t.Errorf("Expected Unknown10=1, got %d", packet.Unknown10) } if packet.NumClassItems != 1 { t.Errorf("Expected NumClassItems=1, got %d", packet.NumClassItems) } if len(packet.ClassItems) != 1 { t.Errorf("Expected 1 class item, got %d", len(packet.ClassItems)) } if packet.ClassItems[0].ClassID != 5 { t.Errorf("Expected ClassID=5, got %d", packet.ClassItems[0].ClassID) } if len(packet.ClassItems[0].StartingItems) != 1 { t.Errorf("Expected 1 starting item, got %d", len(packet.ClassItems[0].StartingItems)) } if packet.ClassItems[0].StartingItems[0].ModelID != 100 { t.Errorf("Expected ModelID=100, got %d", packet.ClassItems[0].StartingItems[0].ModelID) } } func TestLoginReplyMsgV284WithoutClassItems(t *testing.T) { data := []byte{ 0x00, // LoginResponse = 0 0x07, 0x00, 'A', 'n', 't', 'o', 'n', 'i', 'a', // Unknown = "Antonia" 0x00, // ParentalControlFlag = 0 0x00, 0x00, 0x00, 0x00, // ParentalControlTimer = 0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Unknown2 (8 bytes) 0x31, 0xD4, 0x00, 0x00, // CacheSettingAccountID = 54321 0x04, 0x00, 't', 'e', 's', 't', // Unknown3 = "test" 0x00, // ResetAppearance = 0 0x00, // DoNotForceSoga = 0 0x00, 0x00, // Unknown5 = 0 0x00, // Unknown6 = 0 0x00, 0x00, 0x00, 0x00, // Unknown7 = 0 0x00, 0x00, // Unknown8 (2 bytes) 0x00, // Unknown10 = 0 (falsy, so class items will be skipped) 0x00, // UnknownArray2Size = 0 } parser := parser.NewParser(data) var packet LoginReplyMsgV284 err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.Unknown10 != 0 { t.Errorf("Expected Unknown10=0, got %d", packet.Unknown10) } if packet.NumClassItems != 0 { t.Errorf("Expected NumClassItems=0 (should be skipped), got %d", packet.NumClassItems) } if len(packet.ClassItems) != 0 { t.Errorf("Expected 0 class items (should be skipped), got %d", len(packet.ClassItems)) } } func TestVersionRegistryWithLoginReplyMsg(t *testing.T) { registry := parser.NewVersionRegistry() RegisterLoginServerStructs(registry) // Test data for LoginReplyMsgV1 data := []byte{ 0x00, // LoginResponse = 0 0x07, 0x00, 'A', 'n', 't', 'o', 'n', 'i', 'a', // WorldName = "Antonia" 0x00, // ParentalControlFlag = 0 0x00, 0x00, 0x00, 0x00, // ParentalControlTimer[0] = 0 0x00, 0x00, 0x00, 0x00, // ParentalControlTimer[1] = 0 0x00, 0x00, 0x00, 0x00, // ParentalControlNext = 0 0x31, 0xD4, 0x00, 0x00, // AccountID = 54321 } parser := parser.NewParser(data) result, err := parser.ParseWithVersion(registry, "LoginReplyMsg", "1") if err != nil { t.Fatalf("Parse with version error: %v", err) } packet, ok := result.(LoginReplyMsgV1) if !ok { t.Fatal("Expected LoginReplyMsgV1 type") } if packet.LoginResponse != 0 { t.Errorf("Expected LoginResponse=0, got %d", packet.LoginResponse) } if packet.WorldName.Data != "Antonia" { t.Errorf("Expected WorldName='Antonia', got '%s'", packet.WorldName.Data) } } func TestVersionRegistryFallback(t *testing.T) { registry := parser.NewVersionRegistry() RegisterLoginServerStructs(registry) data := []byte{ 0x39, 0x30, 0x00, 0x00, // CharID = 12345 0x08, 0x00, 'T', 'e', 's', 't', 'C', 'h', 'a', 'r', // Name = "TestChar" } parser := parser.NewParser(data) // Request version 150 (doesn't exist), should fall back to version 1 result, err := parser.ParseWithVersion(registry, "PlayRequest", "150") if err != nil { t.Fatalf("Parse with fallback error: %v", err) } // Should get PlayRequestV1 (nearest lower version) packet, ok := result.(PlayRequestV1) if !ok { t.Error("Expected fallback to PlayRequestV1") } if packet.CharID != 12345 { t.Errorf("Expected CharID=12345, got %d", packet.CharID) } } func TestWorldUpdate(t *testing.T) { data := []byte{ 0x01, 0x00, 0x00, 0x00, // ServerID = 1 0x01, // Up = 1 0x00, // Locked = 0 0x00, // Unknown1 = 0 0x32, // Unknown2 = 50 } parser := parser.NewParser(data) var packet WorldUpdate err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } if packet.ServerID != 1 { t.Errorf("Expected ServerID=1, got %d", packet.ServerID) } if packet.Up != 1 { t.Errorf("Expected Up=1, got %d", packet.Up) } if packet.Locked != 0 { t.Errorf("Expected Locked=0, got %d", packet.Locked) } } func TestLoginResponseV60100WithComplexArrays(t *testing.T) { data := []byte{ 0x00, // LoginResponse = 0 0x07, 0x00, 'A', 'n', 't', 'o', 'n', 'i', 'a', // Unknown = "Antonia" 0x00, // ParentalControlFlag = 0 0x00, 0x00, 0x00, 0x00, // ParentalControlTimer = 0 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Unknown2 (8 bytes) 0x21, 0xD4, 0x00, 0x00, // AccountID = 54321 0x04, 0x00, 't', 'e', 's', 't', // Unknown3 = "test" 0x00, // ResetAppearance = 0 0x00, // DoNotForceSoga = 0 0x00, // Unknown4 = 0 0x00, 0x00, // Unknown5 = 0 0x00, 0x00, 0x00, 0x00, 0x00, // Unknown6 (5 bytes) 0x00, 0x00, 0x00, 0x00, // Unknown7 = 0 0x00, 0x00, // Unknown7a = 0 0x00, // RaceUnknown = 0 0x00, 0x00, 0x00, // Unknown8 (3 bytes) 0x00, 0x00, 0x00, // Unknown9 (3 bytes) 0x01, // Unknown10 = 1 (enables class items) 0x01, // NumClassItems = 1 // ClassItem 1 0x05, // ClassID = 5 0x01, // NumItems = 1 // StartingItem 1 0x64, 0x00, 0x00, 0x00, // ModelID = 100 (int32 in newer versions) 0x01, // SlotID = 1 0x01, // UseColor = 1 0x01, // UseHighlightColor = 1 0xFF, 0x00, 0x00, // ModelColor = Red 0x00, 0xFF, 0x00, // ModelHighlightColor = Green 0x00, // UnknownArray2Size = 0 0x00, 0x00, 0x00, 0x00, // Unknown11 = 0 0x00, 0x00, 0x00, 0x00, // SubLevel = 0 0xFF, 0xFF, 0xFF, 0xFF, // RaceFlag = 0xFFFFFFFF 0xFF, 0xFF, 0xFF, 0xFF, // ClassFlag = 0xFFFFFFFF 0x08, 0x00, 't', 'e', 's', 't', 'p', 'a', 's', 's', // Password = "testpass" 0x08, 0x00, 't', 'e', 's', 't', 'u', 's', 'e', 'r', // Username = "testuser" 0x07, 0x00, 's', 'e', 'r', 'v', 'i', 'c', 'e', // Service = "service" 0x01, // Unknown12 = 1 (enables Lvl90 class items) 0x01, // Lvl90NumClassItems = 1 // Lvl90 ClassItem 1 0x06, // ClassID = 6 0x01, // NumItems = 1 // StartingItem 1 0x65, 0x00, 0x00, 0x00, // ModelID = 101 0x02, // SlotID = 2 0x01, // UseColor = 1 0x01, // UseHighlightColor = 1 0x00, 0xFF, 0x00, // ModelColor = Green 0xFF, 0x00, 0x00, // ModelHighlightColor = Red 0x01, // Unknown13 = 1 (enables TimeLocked class items) 0x01, // TimeLockedNumClassItems = 1 // TimeLocked ClassItem 1 0x07, // ClassID = 7 0x01, // NumItems = 1 // StartingItem 1 0x66, 0x00, 0x00, 0x00, // ModelID = 102 0x03, // SlotID = 3 0x00, // UseColor = 0 0x00, // UseHighlightColor = 0 0x80, 0x80, 0x80, // ModelColor = Gray 0x40, 0x40, 0x40, // ModelHighlightColor = Dark gray 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Unknown14 (13 bytes) } parser := parser.NewParser(data) var packet LoginResponseV60100 err := parser.ParseStruct(&packet) if err != nil { t.Fatalf("Parse error: %v", err) } // Test main class items if len(packet.ClassItems) != 1 { t.Errorf("Expected 1 class item, got %d", len(packet.ClassItems)) } if packet.ClassItems[0].ClassID != 5 { t.Errorf("Expected ClassID=5, got %d", packet.ClassItems[0].ClassID) } // Test Lvl90 class items if len(packet.Lvl90ClassItems) != 1 { t.Errorf("Expected 1 Lvl90 class item, got %d", len(packet.Lvl90ClassItems)) } if packet.Lvl90ClassItems[0].ClassID != 6 { t.Errorf("Expected Lvl90 ClassID=6, got %d", packet.Lvl90ClassItems[0].ClassID) } // Test TimeLocked class items if len(packet.TimeLockedClassItems) != 1 { t.Errorf("Expected 1 TimeLocked class item, got %d", len(packet.TimeLockedClassItems)) } if packet.TimeLockedClassItems[0].ClassID != 7 { t.Errorf("Expected TimeLocked ClassID=7, got %d", packet.TimeLockedClassItems[0].ClassID) } if packet.RaceFlag != 0xFFFFFFFF { t.Errorf("Expected RaceFlag=0xFFFFFFFF, got %d", packet.RaceFlag) } }