202 lines
5.0 KiB
Go
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
|
|
}
|