From f6334b97c974bb3d48d4c36511e568c8117032df Mon Sep 17 00:00:00 2001 From: Sky Johnson Date: Wed, 30 Jul 2025 08:56:18 -0500 Subject: [PATCH] implement fixes and move login handlers to packet defs --- cmd/login_server/client.go | 2 +- cmd/login_server/database.go | 27 ++++-- cmd/login_server/handlers.go | 153 ++++++++++++++++++------------- cmd/login_server/login_server.go | 4 + go.mod | 7 +- go.sum | 26 ++++++ internal/udp/connection.go | 5 + 7 files changed, 150 insertions(+), 74 deletions(-) diff --git a/cmd/login_server/client.go b/cmd/login_server/client.go index e0bceb6..a92c8aa 100644 --- a/cmd/login_server/client.go +++ b/cmd/login_server/client.go @@ -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) diff --git a/cmd/login_server/database.go b/cmd/login_server/database.go index b86f9d7..d9f21a3 100644 --- a/cmd/login_server/database.go +++ b/cmd/login_server/database.go @@ -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 + } +} diff --git a/cmd/login_server/handlers.go b/cmd/login_server/handlers.go index b793554..ea0a88c 100644 --- a/cmd/login_server/handlers.go +++ b/cmd/login_server/handlers.go @@ -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) diff --git a/cmd/login_server/login_server.go b/cmd/login_server/login_server.go index 3875620..077f3fd 100644 --- a/cmd/login_server/login_server.go +++ b/cmd/login_server/login_server.go @@ -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 { diff --git a/go.mod b/go.mod index 70826a1..c65c9f5 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index c469a5f..85eb73c 100644 --- a/go.sum +++ b/go.sum @@ -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= diff --git a/internal/udp/connection.go b/internal/udp/connection.go index 86c061a..acde943 100644 --- a/internal/udp/connection.go +++ b/internal/udp/connection.go @@ -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 +}