1
0
EQ2Emu/cmd/loginServer/database.go
2025-07-02 13:14:42 -05:00

202 lines
5.0 KiB
Go

package main
import (
"crypto/sha512"
"fmt"
"time"
"zombiezen.com/go/sqlite"
)
func authenticateUser(conn *sqlite.Conn, username, password string, allowCreate bool) (bool, uint32, error) {
stmt := conn.Prep("SELECT id, password FROM accounts WHERE name = ?")
stmt.BindText(1, username)
if hasRow, err := stmt.Step(); err != nil {
return false, 0, err
} else if !hasRow {
if allowCreate {
return createAccount(conn, username, password)
}
return false, 0, nil
}
accountID := stmt.ColumnInt64(0)
storedPassword := stmt.ColumnText(1)
if verifyPassword(password, storedPassword) {
return true, uint32(accountID), nil
}
return false, 0, nil
}
func createAccount(conn *sqlite.Conn, username, password string) (bool, uint32, error) {
hashedPassword := hashPassword(password)
stmt := conn.Prep("INSERT INTO accounts (name, password, created_date) VALUES (?, ?, ?)")
stmt.BindText(1, username)
stmt.BindText(2, hashedPassword)
stmt.BindInt64(3, time.Now().Unix())
if _, err := stmt.Step(); err != nil {
return false, 0, err
}
accountID := conn.LastInsertRowID()
return true, uint32(accountID), nil
}
func hashPassword(password string) string {
hash := sha512.Sum512([]byte(password))
return fmt.Sprintf("%x", hash)
}
func verifyPassword(password, stored string) bool {
return hashPassword(password) == stored
}
func getCharacterList(conn *sqlite.Conn, accountID uint32) ([]*Character, error) {
stmt := conn.Prep(`
SELECT id, name, race, class, gender, level, current_zone_id,
server_id, created_date, last_played
FROM login_characters
WHERE account_id = ? AND deleted = 0
`)
stmt.BindInt64(1, int64(accountID))
var characters []*Character
for {
hasRow, err := stmt.Step()
if err != nil {
return nil, err
}
if !hasRow {
break
}
char := &Character{
ID: uint32(stmt.ColumnInt64(0)),
Name: stmt.ColumnText(1),
Race: uint8(stmt.ColumnInt64(2)),
Class: uint8(stmt.ColumnInt64(3)),
Gender: uint8(stmt.ColumnInt64(4)),
Level: uint8(stmt.ColumnInt64(5)),
Zone: getZoneName(stmt.ColumnInt64(6)),
ServerID: uint32(stmt.ColumnInt64(7)),
Created: stmt.ColumnInt64(8),
LastLogin: stmt.ColumnInt64(9),
}
characters = append(characters, char)
}
return characters, nil
}
func createCharacter(conn *sqlite.Conn, accountID uint32, req *CreateCharacterRequest) (uint32, error) {
// Check if name is taken
stmt := conn.Prep("SELECT id FROM login_characters WHERE name = ? AND deleted = 0")
stmt.BindText(1, req.Name)
if hasRow, err := stmt.Step(); err != nil {
return 0, err
} else if hasRow {
return 0, fmt.Errorf("name already taken")
}
// Create character
stmt = conn.Prep(`
INSERT INTO login_characters (
account_id, server_id, char_id, name, race, class, gender,
deity, body_size, body_age, created_date
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`)
stmt.BindInt64(1, int64(accountID))
stmt.BindInt64(2, int64(req.ServerID))
stmt.BindInt64(3, generateCharacterID())
stmt.BindText(4, req.Name)
stmt.BindInt64(5, int64(req.Race))
stmt.BindInt64(6, int64(req.Class))
stmt.BindInt64(7, int64(req.Gender))
stmt.BindInt64(8, int64(req.Deity))
stmt.BindFloat(9, float64(req.BodySize))
stmt.BindFloat(10, float64(req.BodyAge))
stmt.BindInt64(11, time.Now().Unix())
if _, err := stmt.Step(); err != nil {
return 0, err
}
return uint32(conn.LastInsertRowID()), nil
}
func deleteCharacter(conn *sqlite.Conn, accountID, characterID uint32) error {
stmt := conn.Prep(`
UPDATE login_characters
SET deleted = 1
WHERE id = ? AND account_id = ?
`)
stmt.BindInt64(1, int64(characterID))
stmt.BindInt64(2, int64(accountID))
_, err := stmt.Step()
return err
}
func generateCharacterID() int64 {
return time.Now().UnixNano() / 1000000 // Use timestamp as unique ID
}
func getZoneName(zoneID int64) string {
// Simple zone mapping - in real implementation this would be from database
zones := map[int64]string{
1: "Qeynos",
2: "Freeport",
3: "Kelethin",
4: "Neriak",
5: "Gorowyn",
6: "New Halas",
7: "Queen's Colony",
8: "Outpost of the Overlord",
}
if name, exists := zones[zoneID]; exists {
return name
}
return "Unknown Zone"
}
func updateCharacterLastLogin(conn *sqlite.Conn, characterID uint32) error {
stmt := conn.Prep("UPDATE login_characters SET last_played = ? WHERE id = ?")
stmt.BindInt64(1, time.Now().Unix())
stmt.BindInt64(2, int64(characterID))
_, err := stmt.Step()
return err
}
func getAccountStats(conn *sqlite.Conn) (int, int, error) {
// Total accounts
stmt := conn.Prep("SELECT COUNT(*) FROM accounts")
if hasRow, err := stmt.Step(); err != nil {
return 0, 0, err
} else if !hasRow {
return 0, 0, nil
}
totalAccounts := int(stmt.ColumnInt64(0))
// Total characters
stmt = conn.Prep("SELECT COUNT(*) FROM login_characters WHERE deleted = 0")
if hasRow, err := stmt.Step(); err != nil {
return 0, 0, err
} else if !hasRow {
return totalAccounts, 0, nil
}
totalCharacters := int(stmt.ColumnInt64(0))
return totalAccounts, totalCharacters, nil
}