implement fixes and move login handlers to packet defs

This commit is contained in:
Sky Johnson 2025-07-30 08:56:18 -05:00
parent ca2ef3a613
commit f6334b97c9
7 changed files with 150 additions and 74 deletions

View File

@ -151,7 +151,7 @@ func (lc *LoginClient) handleLoginRequest(packet *udp.ApplicationPacket) {
// Update account info
account.LastLogin = time.Now()
account.IPAddress = lc.connection.GetStats().State.String() // Get IP from connection
account.IPAddress = lc.connection.GetClientAddr().IP.String()
account.ClientVersion = lc.version
lc.server.database.UpdateAccountLogin(account)

View File

@ -78,7 +78,7 @@ func (d *Database) AuthenticateAccount(username, password string) (*Account, err
var account Account
var passwordHash string
var createdDate, lastLogin string
var lastLogin string
var clientVersion int64
account.ID = int32(stmt.ColumnInt64(0))
@ -86,7 +86,7 @@ func (d *Database) AuthenticateAccount(username, password string) (*Account, err
passwordHash = stmt.ColumnText(2)
account.LSAdmin = stmt.ColumnInt(3) != 0
account.WorldAdmin = stmt.ColumnInt(4) != 0
createdDate = stmt.ColumnText(5)
// Skip created_date at index 5 - not needed for authentication
lastLogin = stmt.ColumnText(6)
clientVersion = stmt.ColumnInt64(7)
@ -170,7 +170,7 @@ func (d *Database) LoadCharacters(accountID int32, version uint16) ([]*Character
characters = append(characters, char)
}
return characters, stmt.Err()
return characters, nil
}
// CharacterNameExists checks if a character name is already taken
@ -242,7 +242,8 @@ func (d *Database) DeleteCharacter(charID, accountID int32) error {
// Check if any rows were affected
stmt, _ := d.conn.Prepare("SELECT changes()")
defer stmt.Finalize()
if stmt.Step() && stmt.ColumnInt(0) == 0 {
hasRow, _ := stmt.Step()
if hasRow && stmt.ColumnInt(0) == 0 {
return fmt.Errorf("character not found or not owned by account")
}
@ -294,12 +295,12 @@ func (d *Database) GetWorldServers() ([]*WorldServer, error) {
}
server.Online = server.Status == "online"
server.PopulationLevel = d.calculatePopulationLevel(server.Population)
server.PopulationLevel = calculatePopulationLevel(server.Population)
servers = append(servers, server)
}
return servers, stmt.Err()
return servers, nil
}
// UpdateWorldServerStats updates world server statistics
@ -393,3 +394,17 @@ func (d *Database) GetAccountBonus(accountID int32) uint8 {
return bonus
}
// calculatePopulationLevel converts population to display level
func calculatePopulationLevel(population int32) uint8 {
switch {
case population >= 1000:
return 3 // Full
case population >= 500:
return 2 // High
case population >= 100:
return 1 // Medium
default:
return 0 // Low
}
}

View File

@ -3,6 +3,7 @@ package main
import (
"encoding/binary"
"eq2emu/internal/common/opcodes"
"eq2emu/internal/packets"
"eq2emu/internal/udp"
"fmt"
"log"
@ -16,61 +17,57 @@ type LoginRequest struct {
Version uint16
}
// opcodeToPacketName maps opcodes to packet definition names
var opcodeToPacketName = map[uint16]string{
opcodes.OpLoginRequestMsg: "LS_LoginRequest",
opcodes.OpAllWSDescRequestMsg: "LS_WorldListRequest",
opcodes.OpAllCharactersDescRequestMsg: "LS_CharacterListRequest",
opcodes.OpCreateCharacterRequestMsg: "LS_CreateCharacterRequest",
opcodes.OpDeleteCharacterRequestMsg: "LS_DeleteCharacterRequest",
opcodes.OpPlayCharacterRequestMsg: "LS_PlayCharacterRequest",
}
// parsePacketWithDefinition parses packet using definitions or fails
func (lc *LoginClient) parsePacketWithDefinition(opcode uint16, data []byte) (map[string]any, error) {
packetName, exists := opcodeToPacketName[opcode]
if !exists {
return nil, fmt.Errorf("no packet name mapping for opcode 0x%04X", opcode)
}
packetDef, exists := packets.GetPacket(packetName)
if !exists {
return nil, fmt.Errorf("no packet definition found for %s", packetName)
}
// Use client version for parsing, default flags
result, err := packetDef.Parse(data, uint32(lc.version), 0)
if err != nil {
return nil, fmt.Errorf("packet parsing failed for %s: %w", packetName, err)
}
return result, nil
}
// 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")
parsed, err := lc.parsePacketWithDefinition(opcodes.OpLoginRequestMsg, data)
if err != nil {
return nil, err
}
offset := 0
req := &LoginRequest{}
// 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")
if username, ok := parsed["username"].(string); ok {
req.Username = username
}
unknown1Len := binary.LittleEndian.Uint16(data[offset:])
offset += 2 + int(unknown1Len)
// Parse username
if offset+2 > len(data) {
return nil, fmt.Errorf("invalid packet format")
if password, ok := parsed["password"].(string); ok {
req.Password = password
}
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:])
if version, ok := parsed["version"].(uint16); ok {
req.Version = version
}
return &LoginRequest{
Username: username,
Password: password,
Version: version,
}, nil
return req, nil
}
// sendLoginDenied sends login failure response
@ -296,16 +293,24 @@ func (lc *LoginClient) handleCharacterCreateRequest(packet *udp.ApplicationPacke
return
}
// Parse character creation data (simplified)
if len(packet.Data) < 20 {
lc.sendCharacterCreateFailed(1) // Generic error
parsed, err := lc.parsePacketWithDefinition(opcodes.OpCreateCharacterRequestMsg, packet.Data)
if err != nil {
log.Printf("Failed to parse character create request: %v", err)
lc.sendCharacterCreateFailed(1)
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])
serverID, ok := parsed["server_id"].(uint32)
if !ok {
lc.sendCharacterCreateFailed(1)
return
}
name, ok := parsed["character_name"].(string)
if !ok {
lc.sendCharacterCreateFailed(1)
return
}
// Validate character name
if len(name) < 3 || len(name) > 20 {
@ -400,13 +405,21 @@ func (lc *LoginClient) handleCharacterDeleteRequest(packet *udp.ApplicationPacke
return
}
// Parse deletion request (simplified)
if len(packet.Data) < 12 {
parsed, err := lc.parsePacketWithDefinition(opcodes.OpDeleteCharacterRequestMsg, packet.Data)
if err != nil {
log.Printf("Failed to parse character delete request: %v", err)
return
}
charID := binary.LittleEndian.Uint32(packet.Data[0:4])
serverID := binary.LittleEndian.Uint32(packet.Data[4:8])
charID, ok := parsed["character_id"].(uint32)
if !ok {
return
}
serverID, ok := parsed["server_id"].(uint32)
if !ok {
return
}
// Verify character belongs to this account
var char *Character
@ -423,7 +436,7 @@ func (lc *LoginClient) handleCharacterDeleteRequest(packet *udp.ApplicationPacke
}
// Mark character as deleted
err := lc.server.database.DeleteCharacter(int32(charID), lc.account.ID)
err = lc.server.database.DeleteCharacter(int32(charID), lc.account.ID)
if err != nil {
log.Printf("Error deleting character: %v", err)
return
@ -438,11 +451,11 @@ func (lc *LoginClient) handleCharacterDeleteRequest(packet *udp.ApplicationPacke
binary.LittleEndian.PutUint32(data[8:], charID)
binary.LittleEndian.PutUint32(data[12:], uint32(lc.account.ID))
packet = &udp.ApplicationPacket{
responsePacket := &udp.ApplicationPacket{
Opcode: opcodes.OpDeleteCharacterReplyMsg,
Data: data,
}
lc.connection.SendPacket(packet)
lc.connection.SendPacket(responsePacket)
lc.sendCharacterList() // Refresh character list
}
@ -454,14 +467,24 @@ func (lc *LoginClient) handlePlayCharacterRequest(packet *udp.ApplicationPacket)
return
}
// Parse play request
if len(packet.Data) < 8 {
parsed, err := lc.parsePacketWithDefinition(opcodes.OpPlayCharacterRequestMsg, packet.Data)
if err != nil {
log.Printf("Failed to parse play character request: %v", err)
lc.sendPlayFailed(1)
return
}
charID := binary.LittleEndian.Uint32(packet.Data[0:4])
serverID := binary.LittleEndian.Uint32(packet.Data[4:8])
charID, ok := parsed["character_id"].(uint32)
if !ok {
lc.sendPlayFailed(1)
return
}
serverID, ok := parsed["server_id"].(uint32)
if !ok {
lc.sendPlayFailed(1)
return
}
// Find world server
world := lc.server.worldList.GetWorld(int32(serverID))
@ -487,7 +510,7 @@ func (lc *LoginClient) handlePlayCharacterRequest(packet *udp.ApplicationPacket)
lc.pendingPlayCharID = int32(charID)
// Send play request to world server
err := lc.server.worldList.SendPlayRequest(world, lc.account.ID, int32(charID))
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)

View File

@ -1,6 +1,7 @@
package main
import (
"eq2emu/internal/packets"
"eq2emu/internal/udp"
"fmt"
"log"
@ -36,6 +37,9 @@ func NewLoginServer(config *Config) (*LoginServer, error) {
ls.stats.StartTime = time.Now()
// Initialize packet definitions
log.Printf("Loaded %d packet definitions", packets.GetPacketCount())
// Initialize database
db, err := NewDatabase(config.Database)
if err != nil {

7
go.mod
View File

@ -2,18 +2,21 @@ module eq2emu
go 1.24.5
require (
golang.org/x/crypto v0.40.0
zombiezen.com/go/sqlite v1.4.2
)
require (
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/sys v0.34.0 // indirect
modernc.org/libc v1.65.7 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.11.0 // indirect
modernc.org/sqlite v1.37.1 // indirect
zombiezen.com/go/sqlite v1.4.2 // indirect
)

26
go.sum
View File

@ -1,5 +1,7 @@
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@ -12,16 +14,40 @@ golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/sync v0.14.0 h1:woo0S4Yywslg6hp4eUFjTVOyKt0RookbpAHG4c1HmhQ=
golang.org/x/sync v0.14.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU=
golang.org/x/tools v0.33.0 h1:4qz2S3zmRxbGIhDIAgjxvFutSvH5EfnsYrRBj0UI0bc=
golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI=
modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s=
modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU=
modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE=
modernc.org/fileutil v1.3.1 h1:8vq5fe7jdtEvoCf3Zf9Nm0Q05sH6kGx0Op2CPx1wTC8=
modernc.org/fileutil v1.3.1/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.65.7 h1:Ia9Z4yzZtWNtUIuiPuQ7Qf7kxYrxP1/jeHZzG8bFu00=
modernc.org/libc v1.65.7/go.mod h1:011EQibzzio/VX3ygj1qGFt5kMjP0lHb0qCW5/D/pQU=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
modernc.org/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs=
modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g=
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
zombiezen.com/go/sqlite v1.4.2 h1:KZXLrBuJ7tKNEm+VJcApLMeQbhmAUOKA5VWS93DfFRo=
zombiezen.com/go/sqlite v1.4.2/go.mod h1:5Kd4taTAD4MkBzT25mQ9uaAlLjyR0rFhsR6iINO70jc=

View File

@ -551,3 +551,8 @@ func (c *Connection) IsTimedOut() bool {
defer c.mutex.RUnlock()
return time.Since(c.lastActivity) > c.config.Timeout
}
// GetClientAddr returns the client's UDP address
func (c *Connection) GetClientAddr() *net.UDPAddr {
return c.addr
}