eq2go/cmd/login_server/handlers.go

514 lines
13 KiB
Go

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)
}