package main import ( "encoding/binary" "eq2emu/internal/common/opcodes" "eq2emu/internal/udp" "fmt" "log" "time" ) // LoginRequest represents parsed login request data type LoginRequest struct { Username string Password string Version uint16 } // parseLoginRequest parses the login request packet data func (lc *LoginClient) parseLoginRequest(data []byte) (*LoginRequest, error) { if len(data) < 10 { return nil, fmt.Errorf("packet too small") } offset := 0 // Skip access code (16-bit string) accessCodeLen := binary.LittleEndian.Uint16(data[offset:]) offset += 2 + int(accessCodeLen) // Skip unknown1 (16-bit string) if offset+2 > len(data) { return nil, fmt.Errorf("invalid packet format") } unknown1Len := binary.LittleEndian.Uint16(data[offset:]) offset += 2 + int(unknown1Len) // Parse username if offset+2 > len(data) { return nil, fmt.Errorf("invalid packet format") } usernameLen := binary.LittleEndian.Uint16(data[offset:]) offset += 2 if offset+int(usernameLen) > len(data) { return nil, fmt.Errorf("invalid username length") } username := string(data[offset : offset+int(usernameLen)]) offset += int(usernameLen) // Parse password if offset+2 > len(data) { return nil, fmt.Errorf("invalid packet format") } passwordLen := binary.LittleEndian.Uint16(data[offset:]) offset += 2 if offset+int(passwordLen) > len(data) { return nil, fmt.Errorf("invalid password length") } password := string(data[offset : offset+int(passwordLen)]) offset += int(passwordLen) // Skip unknown fields and get version version := uint16(0) if offset+18 <= len(data) { // Skip 4 unknown strings + version // Simple parsing - in real implementation, parse all unknown fields properly version = binary.LittleEndian.Uint16(data[len(data)-10:]) } return &LoginRequest{ Username: username, Password: password, Version: version, }, nil } // sendLoginDenied sends login failure response func (lc *LoginClient) sendLoginDenied() { data := make([]byte, 12) data[0] = 1 // reply_code: Invalid username or password binary.LittleEndian.PutUint32(data[4:], 0xFFFFFFFF) binary.LittleEndian.PutUint32(data[8:], 0xFFFFFFFF) packet := &udp.ApplicationPacket{ Opcode: opcodes.OpLoginReplyMsg, Data: data, } lc.connection.SendPacket(packet) // Disconnect after short delay time.AfterFunc(1*time.Second, func() { lc.Disconnect() }) } // sendLoginDeniedBadVersion sends bad version response func (lc *LoginClient) sendLoginDeniedBadVersion() { data := make([]byte, 12) data[0] = 6 // reply_code: Version mismatch binary.LittleEndian.PutUint32(data[4:], 0xFFFFFFFF) binary.LittleEndian.PutUint32(data[8:], 0xFFFFFFFF) packet := &udp.ApplicationPacket{ Opcode: opcodes.OpLoginReplyMsg, Data: data, } lc.connection.SendPacket(packet) time.AfterFunc(1*time.Second, func() { lc.Disconnect() }) } // sendLoginAccepted sends successful login response func (lc *LoginClient) sendLoginAccepted() { config := lc.server.GetConfig() // Build login response packet data := make([]byte, 64) // Base size, will expand as needed offset := 0 // Account ID binary.LittleEndian.PutUint32(data[offset:], uint32(lc.account.ID)) offset += 4 // Login response code (0 = success) data[offset] = 0 offset++ // Do not force SOGA flag data[offset] = 1 offset++ // Subscription level binary.LittleEndian.PutUint32(data[offset:], config.DefaultSubscriptionLevel) offset += 4 // Race flags (enabled races) binary.LittleEndian.PutUint32(data[offset:], 0x1FFFFF) offset += 4 // Class flags (enabled classes) binary.LittleEndian.PutUint32(data[offset:], 0x7FFFFFE) offset += 4 // Username (16-bit string) username := lc.account.Username binary.LittleEndian.PutUint16(data[offset:], uint16(len(username))) offset += 2 copy(data[offset:], username) offset += len(username) // Expansion flags binary.LittleEndian.PutUint16(data[offset:], config.ExpansionFlag) offset += 2 // Additional flags data[offset] = 0xFF data[offset+1] = 0xFF data[offset+2] = 0xFF offset += 3 // Class access flag data[offset] = 0xFF offset++ // Enabled races binary.LittleEndian.PutUint32(data[offset:], config.EnabledRaces) offset += 4 // Cities flag data[offset] = config.CitiesFlag offset++ packet := &udp.ApplicationPacket{ Opcode: opcodes.OpLoginReplyMsg, Data: data[:offset], } lc.connection.SendPacket(packet) } // sendWorldList sends available world servers to client func (lc *LoginClient) sendWorldList() { worlds := lc.server.worldList.GetActiveWorlds() // Build world list packet data := make([]byte, 0, 1024) // Number of worlds worldCount := uint8(len(worlds)) data = append(data, worldCount) for _, world := range worlds { // World ID worldID := make([]byte, 4) binary.LittleEndian.PutUint32(worldID, uint32(world.ID)) data = append(data, worldID...) // World name (16-bit string) nameLen := make([]byte, 2) binary.LittleEndian.PutUint16(nameLen, uint16(len(world.Name))) data = append(data, nameLen...) data = append(data, []byte(world.Name)...) // World status flags var flags uint8 if world.Online { flags |= 0x01 } if world.Locked { flags |= 0x02 } if world.Hidden { flags |= 0x04 } data = append(data, flags) // Population (0-3, where 3 = full) data = append(data, world.PopulationLevel) } packet := &udp.ApplicationPacket{ Opcode: opcodes.OpWorldListMsg, Data: data, } lc.connection.SendPacket(packet) } // sendCharacterList sends character list to client func (lc *LoginClient) sendCharacterList() { if lc.account == nil { return } data := make([]byte, 0, 2048) // Number of characters charCount := uint8(len(lc.account.Characters)) data = append(data, charCount) // Character data for _, char := range lc.account.Characters { if char.Deleted { continue } // Character ID charID := make([]byte, 4) binary.LittleEndian.PutUint32(charID, uint32(char.ID)) data = append(data, charID...) // Server ID serverID := make([]byte, 4) binary.LittleEndian.PutUint32(serverID, uint32(char.ServerID)) data = append(data, serverID...) // Character name (16-bit string) nameLen := make([]byte, 2) binary.LittleEndian.PutUint16(nameLen, uint16(len(char.Name))) data = append(data, nameLen...) data = append(data, []byte(char.Name)...) // Character stats data = append(data, byte(char.Race)) data = append(data, byte(char.Gender)) data = append(data, byte(char.Class)) data = append(data, byte(char.Level)) // Creation timestamp timestamp := make([]byte, 4) binary.LittleEndian.PutUint32(timestamp, uint32(char.CreatedDate.Unix())) data = append(data, timestamp...) } // Account info accountID := make([]byte, 4) binary.LittleEndian.PutUint32(accountID, uint32(lc.account.ID)) data = append(data, accountID...) // Max characters data = append(data, 0xFF, 0xFF, 0xFF, 0xFF) // unknown1 data = append(data, 0x00, 0x00) // unknown2 data = append(data, 0x07, 0x00, 0x00, 0x00) // max chars (7) data = append(data, 0x00) // unknown4 packet := &udp.ApplicationPacket{ Opcode: opcodes.OpAllCharactersDescReplyMsg, Data: data, } lc.connection.SendPacket(packet) } // handleCharacterCreateRequest processes character creation func (lc *LoginClient) handleCharacterCreateRequest(packet *udp.ApplicationPacket) { if !lc.authenticated { lc.Disconnect() return } // Parse character creation data (simplified) if len(packet.Data) < 20 { lc.sendCharacterCreateFailed(1) // Generic error return } // Extract basic info (this would need full parsing in real implementation) serverID := binary.LittleEndian.Uint32(packet.Data[0:4]) nameLen := binary.LittleEndian.Uint16(packet.Data[4:6]) name := string(packet.Data[6 : 6+nameLen]) // Validate character name if len(name) < 3 || len(name) > 20 { lc.sendCharacterCreateFailed(9) // Bad name length return } // Check if name is taken exists, err := lc.server.database.CharacterNameExists(name, int32(serverID)) if err != nil { log.Printf("Error checking character name: %v", err) lc.sendCharacterCreateFailed(1) return } if exists { lc.sendCharacterCreateFailed(12) // Name taken return } // Create character in database char := &Character{ AccountID: lc.account.ID, ServerID: int32(serverID), Name: name, Level: 1, Race: 1, // Would be parsed from packet Gender: 1, // Would be parsed from packet Class: 1, // Would be parsed from packet CreatedDate: time.Now(), } charID, err := lc.server.database.CreateCharacter(char) if err != nil { log.Printf("Error creating character: %v", err) lc.sendCharacterCreateFailed(1) return } char.ID = charID lc.account.Characters = append(lc.account.Characters, char) lc.sendCharacterCreateSuccess(char) lc.sendCharacterList() // Refresh character list } // sendCharacterCreateSuccess sends successful character creation response func (lc *LoginClient) sendCharacterCreateSuccess(char *Character) { data := make([]byte, 64) offset := 0 // Account ID binary.LittleEndian.PutUint32(data[offset:], uint32(lc.account.ID)) offset += 4 // Response code (0 = success) binary.LittleEndian.PutUint32(data[offset:], 0) offset += 4 // Character name nameLen := uint16(len(char.Name)) binary.LittleEndian.PutUint16(data[offset:], nameLen) offset += 2 copy(data[offset:], char.Name) offset += int(nameLen) packet := &udp.ApplicationPacket{ Opcode: opcodes.OpCreateCharacterReplyMsg, Data: data[:offset], } lc.connection.SendPacket(packet) } // sendCharacterCreateFailed sends character creation failure response func (lc *LoginClient) sendCharacterCreateFailed(reason uint8) { data := make([]byte, 16) binary.LittleEndian.PutUint32(data[0:], uint32(lc.account.ID)) data[4] = reason packet := &udp.ApplicationPacket{ Opcode: opcodes.OpCreateCharacterReplyMsg, Data: data, } lc.connection.SendPacket(packet) } // handleCharacterDeleteRequest processes character deletion func (lc *LoginClient) handleCharacterDeleteRequest(packet *udp.ApplicationPacket) { if !lc.authenticated { lc.Disconnect() return } // Parse deletion request (simplified) if len(packet.Data) < 12 { return } charID := binary.LittleEndian.Uint32(packet.Data[0:4]) serverID := binary.LittleEndian.Uint32(packet.Data[4:8]) // Verify character belongs to this account var char *Character for _, c := range lc.account.Characters { if c.ID == int32(charID) && c.ServerID == int32(serverID) { char = c break } } if char == nil { log.Printf("Account %d attempted to delete character %d that doesn't belong to them", lc.account.ID, charID) return } // Mark character as deleted err := lc.server.database.DeleteCharacter(int32(charID), lc.account.ID) if err != nil { log.Printf("Error deleting character: %v", err) return } char.Deleted = true // Send deletion response data := make([]byte, 24) data[0] = 1 // Success binary.LittleEndian.PutUint32(data[4:], serverID) binary.LittleEndian.PutUint32(data[8:], charID) binary.LittleEndian.PutUint32(data[12:], uint32(lc.account.ID)) packet = &udp.ApplicationPacket{ Opcode: opcodes.OpDeleteCharacterReplyMsg, Data: data, } lc.connection.SendPacket(packet) lc.sendCharacterList() // Refresh character list } // handlePlayCharacterRequest processes character selection for gameplay func (lc *LoginClient) handlePlayCharacterRequest(packet *udp.ApplicationPacket) { if !lc.authenticated { lc.Disconnect() return } // Parse play request if len(packet.Data) < 8 { lc.sendPlayFailed(1) return } charID := binary.LittleEndian.Uint32(packet.Data[0:4]) serverID := binary.LittleEndian.Uint32(packet.Data[4:8]) // Find world server world := lc.server.worldList.GetWorld(int32(serverID)) if world == nil || !world.Online { lc.sendPlayFailed(2) // Server unavailable return } // Verify character ownership var char *Character for _, c := range lc.account.Characters { if c.ID == int32(charID) && c.ServerID == int32(serverID) { char = c break } } if char == nil { lc.sendPlayFailed(1) // Character not found return } lc.pendingPlayCharID = int32(charID) // Send play request to world server err := lc.server.worldList.SendPlayRequest(world, lc.account.ID, int32(charID)) if err != nil { log.Printf("Error sending play request to world server: %v", err) lc.sendPlayFailed(1) return } // World server will respond with connection details log.Printf("Account %s requesting to play character %s on server %s", lc.account.Username, char.Name, world.Name) } // sendPlayFailed sends play character failure response func (lc *LoginClient) sendPlayFailed(reason uint8) { data := make([]byte, 16) data[0] = reason binary.LittleEndian.PutUint32(data[4:], uint32(lc.account.ID)) packet := &udp.ApplicationPacket{ Opcode: opcodes.OpPlayCharacterReplyMsg, Data: data, } lc.connection.SendPacket(packet) }