231 lines
5.8 KiB
Go
231 lines
5.8 KiB
Go
package main
|
|
|
|
import (
|
|
"eq2emu/internal/common/opcodes"
|
|
"eq2emu/internal/udp"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
// LoginClient represents a connected client session
|
|
type LoginClient struct {
|
|
connection *udp.Connection
|
|
server *LoginServer
|
|
account *Account
|
|
lastActivity time.Time
|
|
authenticated bool
|
|
version uint16
|
|
sessionID string
|
|
|
|
// Client state
|
|
needsWorldList bool
|
|
sentCharacterList bool
|
|
pendingPlayCharID int32
|
|
createRequest *CharacterCreateRequest
|
|
}
|
|
|
|
// Account represents an authenticated user account
|
|
type Account struct {
|
|
ID int32
|
|
Username string
|
|
LSAdmin bool
|
|
WorldAdmin bool
|
|
Characters []*Character
|
|
LastLogin time.Time
|
|
IPAddress string
|
|
ClientVersion uint16
|
|
}
|
|
|
|
// Character represents a character in the database
|
|
type Character struct {
|
|
ID int32
|
|
AccountID int32
|
|
ServerID int32
|
|
Name string
|
|
Level int8
|
|
Race int8
|
|
Class int8
|
|
Gender int8
|
|
CreatedDate time.Time
|
|
Deleted bool
|
|
}
|
|
|
|
// CharacterCreateRequest holds pending character creation data
|
|
type CharacterCreateRequest struct {
|
|
ServerID int32
|
|
Name string
|
|
Race int8
|
|
Gender int8
|
|
Class int8
|
|
Face int8
|
|
Hair int8
|
|
HairColor int8
|
|
SkinColor int8
|
|
EyeColor int8
|
|
Timestamp time.Time
|
|
}
|
|
|
|
// NewLoginClient creates a new login client instance
|
|
func NewLoginClient(conn *udp.Connection, server *LoginServer) *LoginClient {
|
|
return &LoginClient{
|
|
connection: conn,
|
|
server: server,
|
|
lastActivity: time.Now(),
|
|
sessionID: fmt.Sprintf("%d", conn.GetSessionID()),
|
|
needsWorldList: true,
|
|
sentCharacterList: false,
|
|
}
|
|
}
|
|
|
|
// ProcessPacket handles incoming packets from the client
|
|
func (lc *LoginClient) ProcessPacket(packet *udp.ApplicationPacket) {
|
|
lc.lastActivity = time.Now()
|
|
|
|
switch packet.Opcode {
|
|
case opcodes.OpLoginRequestMsg:
|
|
lc.handleLoginRequest(packet)
|
|
case opcodes.OpAllWSDescRequestMsg:
|
|
lc.handleWorldListRequest(packet)
|
|
case opcodes.OpAllCharactersDescRequestMsg:
|
|
lc.handleCharacterListRequest(packet)
|
|
case opcodes.OpCreateCharacterRequestMsg:
|
|
lc.handleCharacterCreateRequest(packet)
|
|
case opcodes.OpDeleteCharacterRequestMsg:
|
|
lc.handleCharacterDeleteRequest(packet)
|
|
case opcodes.OpPlayCharacterRequestMsg:
|
|
lc.handlePlayCharacterRequest(packet)
|
|
case opcodes.OpKeymapLoadMsg:
|
|
// Client keymap request - usually ignored
|
|
break
|
|
default:
|
|
log.Printf("Unknown packet opcode from client %s: 0x%04X", lc.sessionID, packet.Opcode)
|
|
}
|
|
}
|
|
|
|
// handleLoginRequest processes client login attempts
|
|
func (lc *LoginClient) handleLoginRequest(packet *udp.ApplicationPacket) {
|
|
lc.server.IncrementLoginAttempts()
|
|
|
|
// Parse login request packet
|
|
loginReq, err := lc.parseLoginRequest(packet.Data)
|
|
if err != nil {
|
|
log.Printf("Failed to parse login request from %s: %v", lc.sessionID, err)
|
|
lc.sendLoginDenied()
|
|
return
|
|
}
|
|
|
|
lc.version = loginReq.Version
|
|
|
|
// Check if client version is supported
|
|
if !lc.server.GetConfig().IsVersionSupported(lc.version) {
|
|
log.Printf("Unsupported client version %d from %s", lc.version, lc.sessionID)
|
|
lc.sendLoginDeniedBadVersion()
|
|
return
|
|
}
|
|
|
|
// Authenticate with database
|
|
account, err := lc.server.database.AuthenticateAccount(loginReq.Username, loginReq.Password)
|
|
if err != nil {
|
|
log.Printf("Authentication failed for %s: %v", loginReq.Username, err)
|
|
lc.sendLoginDenied()
|
|
return
|
|
}
|
|
|
|
if account == nil {
|
|
log.Printf("Invalid credentials for %s", loginReq.Username)
|
|
lc.sendLoginDenied()
|
|
return
|
|
}
|
|
|
|
// Check for existing session
|
|
lc.server.clientMutex.RLock()
|
|
for _, existingClient := range lc.server.clients {
|
|
if existingClient.account != nil && existingClient.account.ID == account.ID && existingClient != lc {
|
|
log.Printf("Account %s already logged in, disconnecting previous session", account.Username)
|
|
existingClient.Disconnect()
|
|
break
|
|
}
|
|
}
|
|
lc.server.clientMutex.RUnlock()
|
|
|
|
// Update account info
|
|
account.LastLogin = time.Now()
|
|
account.IPAddress = lc.connection.GetClientAddr().IP.String()
|
|
account.ClientVersion = lc.version
|
|
lc.server.database.UpdateAccountLogin(account)
|
|
|
|
lc.account = account
|
|
lc.authenticated = true
|
|
lc.server.IncrementSuccessfulLogins()
|
|
|
|
log.Printf("User %s successfully authenticated", account.Username)
|
|
lc.sendLoginAccepted()
|
|
}
|
|
|
|
// handleWorldListRequest sends the list of available world servers
|
|
func (lc *LoginClient) handleWorldListRequest(packet *udp.ApplicationPacket) {
|
|
if !lc.authenticated {
|
|
lc.Disconnect()
|
|
return
|
|
}
|
|
|
|
lc.sendWorldList()
|
|
lc.needsWorldList = false
|
|
|
|
// Load and send character list
|
|
if !lc.sentCharacterList {
|
|
characters, err := lc.server.database.LoadCharacters(lc.account.ID, lc.version)
|
|
if err != nil {
|
|
log.Printf("Failed to load characters for account %d: %v", lc.account.ID, err)
|
|
} else {
|
|
lc.account.Characters = characters
|
|
lc.sentCharacterList = true
|
|
}
|
|
lc.sendCharacterList()
|
|
}
|
|
}
|
|
|
|
// handleCharacterListRequest handles explicit character list requests
|
|
func (lc *LoginClient) handleCharacterListRequest(packet *udp.ApplicationPacket) {
|
|
if !lc.authenticated {
|
|
lc.Disconnect()
|
|
return
|
|
}
|
|
|
|
lc.sendCharacterList()
|
|
}
|
|
|
|
// IsStale returns true if the client connection should be cleaned up
|
|
func (lc *LoginClient) IsStale() bool {
|
|
return time.Since(lc.lastActivity) > 5*time.Minute
|
|
}
|
|
|
|
// Disconnect closes the client connection and cleans up
|
|
func (lc *LoginClient) Disconnect() {
|
|
if lc.connection != nil {
|
|
lc.connection.Close()
|
|
}
|
|
|
|
// Clean up any pending requests
|
|
lc.createRequest = nil
|
|
|
|
log.Printf("Client %s disconnected", lc.sessionID)
|
|
lc.server.RemoveClient(lc.sessionID)
|
|
}
|
|
|
|
// GetAccount returns the authenticated account
|
|
func (lc *LoginClient) GetAccount() *Account {
|
|
return lc.account
|
|
}
|
|
|
|
// GetVersion returns the client version
|
|
func (lc *LoginClient) GetVersion() uint16 {
|
|
return lc.version
|
|
}
|
|
|
|
// GetSessionID returns the session identifier
|
|
func (lc *LoginClient) GetSessionID() string {
|
|
return lc.sessionID
|
|
}
|