diff --git a/cmd/loginServer/clients.go b/cmd/loginServer/clients.go
new file mode 100644
index 0000000..c6a903f
--- /dev/null
+++ b/cmd/loginServer/clients.go
@@ -0,0 +1,90 @@
+package main
+
+import (
+ "sync"
+ "time"
+
+ "github.com/panjf2000/gnet/v2"
+ "zombiezen.com/go/sqlite/sqlitex"
+)
+
+type ClientState int
+
+const (
+ StateConnected ClientState = iota
+ StateAuthenticated
+ StateCharacterSelect
+ StateDisconnected
+)
+
+type Client struct {
+ conn gnet.Conn
+ address string
+ connected time.Time
+ lastActive time.Time
+ state ClientState
+ accountID uint32
+ accountName string
+ version int16
+ mu sync.RWMutex
+}
+
+type ClientManager struct {
+ clients map[int]*Client
+ db *sqlitex.Pool
+ mu sync.RWMutex
+}
+
+func NewClientManager(db *sqlitex.Pool) *ClientManager {
+ return &ClientManager{
+ clients: make(map[int]*Client),
+ db: db,
+ }
+}
+
+func (cm *ClientManager) AddClient(fd int, client *Client) {
+ cm.mu.Lock()
+ defer cm.mu.Unlock()
+ cm.clients[fd] = client
+}
+
+func (cm *ClientManager) RemoveClient(fd int) {
+ cm.mu.Lock()
+ defer cm.mu.Unlock()
+ delete(cm.clients, fd)
+}
+
+func (cm *ClientManager) GetClient(fd int) *Client {
+ cm.mu.RLock()
+ defer cm.mu.RUnlock()
+ return cm.clients[fd]
+}
+
+func (cm *ClientManager) CleanupExpired() {
+ cm.mu.Lock()
+ defer cm.mu.Unlock()
+
+ cutoff := time.Now().Add(-30 * time.Minute)
+ for fd, client := range cm.clients {
+ if client.lastActive.Before(cutoff) {
+ client.conn.Close()
+ delete(cm.clients, fd)
+ }
+ }
+}
+
+func (cm *ClientManager) GetStats() (int, int) {
+ cm.mu.RLock()
+ defer cm.mu.RUnlock()
+
+ total := len(cm.clients)
+ authenticated := 0
+
+ for _, client := range cm.clients {
+ if client.state == StateAuthenticated {
+ authenticated++
+ }
+ }
+
+ return total, authenticated
+}
diff --git a/cmd/loginServer/config.go b/cmd/loginServer/config.go
new file mode 100644
index 0000000..2c37e03
--- /dev/null
+++ b/cmd/loginServer/config.go
@@ -0,0 +1,86 @@
+package main
+
+import (
+ "os"
+
+ "github.com/goccy/go-json"
+)
+
+type Config struct {
+ Server ServerConfig `json:"server"`
+ Database DatabaseConfig `json:"database"`
+ Web WebConfig `json:"web"`
+ Game GameConfig `json:"game"`
+}
+
+type ServerConfig struct {
+ Port int `json:"port"`
+ MaxClients int `json:"max_clients"`
+ MaxWorldServers int `json:"max_world_servers"`
+}
+
+type DatabaseConfig struct {
+ Path string `json:"path"`
+}
+
+type WebConfig struct {
+ Enabled bool `json:"enabled"`
+ Address string `json:"address"`
+ Port int `json:"port"`
+ CertFile string `json:"cert_file"`
+ KeyFile string `json:"key_file"`
+ Username string `json:"username"`
+ Password string `json:"password"`
+}
+
+type GameConfig struct {
+ AllowAccountCreation bool `json:"allow_account_creation"`
+ ExpansionFlag uint32 `json:"expansion_flag"`
+ CitiesFlag uint8 `json:"cities_flag"`
+ DefaultSubscriptionLevel uint32 `json:"default_subscription_level"`
+ EnabledRaces uint32 `json:"enabled_races"`
+ MaxCharactersPerAccount int `json:"max_characters_per_account"`
+}
+
+func LoadConfig(filename string) (*Config, error) {
+ data, err := os.ReadFile(filename)
+ if err != nil {
+ // Return default config if file doesn't exist
+ return getDefaultConfig(), nil
+ }
+
+ var config Config
+ if err := json.Unmarshal(data, &config); err != nil {
+ return nil, err
+ }
+
+ return &config, nil
+}
+
+func getDefaultConfig() *Config {
+ return &Config{
+ Server: ServerConfig{
+ Port: 5998,
+ MaxClients: 1000,
+ MaxWorldServers: 10,
+ },
+ Database: DatabaseConfig{
+ Path: "login.db",
+ },
+ Web: WebConfig{
+ Enabled: true,
+ Address: "0.0.0.0",
+ Port: 8080,
+ Username: "admin",
+ Password: "password",
+ },
+ Game: GameConfig{
+ AllowAccountCreation: true,
+ ExpansionFlag: 0x7CFF,
+ CitiesFlag: 0xFF,
+ DefaultSubscriptionLevel: 0xFFFFFFFF,
+ EnabledRaces: 0xFFFF,
+ MaxCharactersPerAccount: 7,
+ },
+ }
+}
diff --git a/cmd/loginServer/database.go b/cmd/loginServer/database.go
new file mode 100644
index 0000000..3c81315
--- /dev/null
+++ b/cmd/loginServer/database.go
@@ -0,0 +1,201 @@
+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
+}
diff --git a/cmd/loginServer/main.go b/cmd/loginServer/main.go
new file mode 100644
index 0000000..2ef5a80
--- /dev/null
+++ b/cmd/loginServer/main.go
@@ -0,0 +1,267 @@
+package main
+
+import (
+ "context"
+ "log"
+ "os"
+ "os/signal"
+ "sync"
+ "syscall"
+ "time"
+
+ "github.com/panjf2000/gnet/v2"
+ "github.com/valyala/fasthttp"
+ "zombiezen.com/go/sqlite"
+ "zombiezen.com/go/sqlite/sqlitex"
+)
+
+type LoginServer struct {
+ config *Config
+ db *sqlitex.Pool
+ clients *ClientManager
+ worlds *WorldManager
+ opcodes map[int16]*OpcodeManager
+ webServer *fasthttp.Server
+ ctx context.Context
+ cancel context.CancelFunc
+ mu sync.RWMutex
+ running bool
+ startTime time.Time
+}
+
+func main() {
+ server := &LoginServer{
+ startTime: time.Now(),
+ }
+
+ if err := server.initialize(); err != nil {
+ log.Fatalf("Failed to initialize server: %v", err)
+ }
+
+ server.printHeader()
+
+ if err := server.start(); err != nil {
+ log.Fatalf("Failed to start server: %v", err)
+ }
+
+ // Wait for shutdown signal
+ sigCh := make(chan os.Signal, 1)
+ signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
+ <-sigCh
+
+ log.Println("Shutting down...")
+ server.shutdown()
+}
+
+func (s *LoginServer) initialize() error {
+ s.ctx, s.cancel = context.WithCancel(context.Background())
+
+ // Load configuration
+ config, err := LoadConfig("login_config.json")
+ if err != nil {
+ return err
+ }
+ s.config = config
+
+ // Initialize database
+ if err := s.initDatabase(); err != nil {
+ return err
+ }
+
+ // Initialize managers
+ s.clients = NewClientManager(s.db)
+ s.worlds = NewWorldManager(s.db)
+
+ // Load opcodes
+ if err := s.loadOpcodes(); err != nil {
+ return err
+ }
+
+ // Initialize web server
+ s.initWebServer()
+
+ return nil
+}
+
+func (s *LoginServer) initDatabase() error {
+ pool, err := sqlitex.Open(s.config.Database.Path, 0, 10)
+ if err != nil {
+ return err
+ }
+ s.db = pool
+
+ // Create tables
+ conn := s.db.Get(s.ctx)
+ defer s.db.Put(conn)
+
+ return s.createTables(conn)
+}
+
+func (s *LoginServer) createTables(conn *sqlite.Conn) error {
+ schema := `
+ CREATE TABLE IF NOT EXISTS accounts (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT UNIQUE NOT NULL,
+ password TEXT NOT NULL,
+ created_date INTEGER DEFAULT CURRENT_TIMESTAMP,
+ ip_address TEXT,
+ last_client_version INTEGER
+ );
+
+ CREATE TABLE IF NOT EXISTS login_characters (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ account_id INTEGER NOT NULL,
+ server_id INTEGER NOT NULL,
+ char_id INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ race INTEGER NOT NULL,
+ class INTEGER NOT NULL,
+ gender INTEGER NOT NULL,
+ deity INTEGER NOT NULL,
+ body_size REAL NOT NULL,
+ body_age REAL NOT NULL,
+ level INTEGER DEFAULT 1,
+ current_zone_id INTEGER DEFAULT 1,
+ created_date INTEGER DEFAULT CURRENT_TIMESTAMP,
+ last_played INTEGER,
+ deleted INTEGER DEFAULT 0,
+ FOREIGN KEY(account_id) REFERENCES accounts(id)
+ );
+
+ CREATE TABLE IF NOT EXISTS login_worldservers (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ account TEXT UNIQUE NOT NULL,
+ name TEXT NOT NULL,
+ password TEXT NOT NULL,
+ admin_id INTEGER DEFAULT 0,
+ disabled INTEGER DEFAULT 0,
+ ip_address TEXT,
+ lastseen INTEGER
+ );
+
+ CREATE TABLE IF NOT EXISTS login_equipment (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ login_characters_id INTEGER NOT NULL,
+ equip_type INTEGER NOT NULL,
+ red INTEGER NOT NULL,
+ green INTEGER NOT NULL,
+ blue INTEGER NOT NULL,
+ highlight_red INTEGER NOT NULL,
+ highlight_green INTEGER NOT NULL,
+ highlight_blue INTEGER NOT NULL,
+ slot INTEGER NOT NULL,
+ FOREIGN KEY(login_characters_id) REFERENCES login_characters(id)
+ );`
+
+ return sqlitex.ExecScript(conn, schema)
+}
+
+func (s *LoginServer) loadOpcodes() error {
+ s.opcodes = make(map[int16]*OpcodeManager)
+
+ // For demo, loading basic opcodes - in real implementation,
+ // these would be loaded from database
+ manager := &OpcodeManager{
+ opcodes: map[string]uint16{
+ "OP_LoginRequestMsg": 0x0001,
+ "OP_LoginReplyMsg": 0x0002,
+ "OP_AllWSDescRequestMsg": 0x0003,
+ "OP_CreateCharacterRequestMsg": 0x0004,
+ "OP_PlayCharacterRequestMsg": 0x0005,
+ "OP_DeleteCharacterRequestMsg": 0x0006,
+ },
+ }
+
+ s.opcodes[1208] = manager // Default version
+ return nil
+}
+
+func (s *LoginServer) initWebServer() {
+ router := &WebRouter{server: s}
+ s.webServer = &fasthttp.Server{
+ Handler: router.Handler,
+ Name: "EQ2LoginWeb",
+ }
+}
+
+func (s *LoginServer) start() error {
+ s.mu.Lock()
+ s.running = true
+ s.mu.Unlock()
+
+ // Start TCP server for game clients
+ go func() {
+ tcpServer := &TCPServer{
+ server: s,
+ clients: s.clients,
+ worlds: s.worlds,
+ }
+
+ log.Printf("Starting TCP server on port %d", s.config.Server.Port)
+ if err := gnet.Run(tcpServer,
+ "tcp://:"+string(rune(s.config.Server.Port)),
+ gnet.WithMulticore(true),
+ gnet.WithTCPKeepAlive(time.Minute*5)); err != nil {
+ log.Printf("TCP server error: %v", err)
+ }
+ }()
+
+ // Start web server
+ if s.config.Web.Enabled {
+ go func() {
+ addr := s.config.Web.Address + ":" + string(rune(s.config.Web.Port))
+ log.Printf("Starting web server on %s", addr)
+ if err := s.webServer.ListenAndServe(addr); err != nil {
+ log.Printf("Web server error: %v", err)
+ }
+ }()
+ }
+
+ // Start periodic tasks
+ go s.runPeriodicTasks()
+
+ return nil
+}
+
+func (s *LoginServer) runPeriodicTasks() {
+ ticker := time.NewTicker(time.Minute)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ticker.C:
+ s.clients.CleanupExpired()
+ s.worlds.UpdateStats()
+ case <-s.ctx.Done():
+ return
+ }
+ }
+}
+
+func (s *LoginServer) shutdown() {
+ s.mu.Lock()
+ s.running = false
+ s.mu.Unlock()
+
+ s.cancel()
+
+ if s.webServer != nil {
+ s.webServer.Shutdown()
+ }
+
+ if s.db != nil {
+ s.db.Close()
+ }
+}
+
+func (s *LoginServer) printHeader() {
+ log.Println("===============================================")
+ log.Println(" EverQuest II Login Server - Go Edition")
+ log.Println(" High Performance Game Authentication Server")
+ log.Println("===============================================")
+ log.Printf("Server Port: %d", s.config.Server.Port)
+ if s.config.Web.Enabled {
+ log.Printf("Web Interface: %s:%d", s.config.Web.Address, s.config.Web.Port)
+ }
+ log.Println("Server starting...")
+}
diff --git a/cmd/loginServer/opcodes.go b/cmd/loginServer/opcodes.go
new file mode 100644
index 0000000..d8d01e7
--- /dev/null
+++ b/cmd/loginServer/opcodes.go
@@ -0,0 +1,143 @@
+package main
+
+import (
+ "crypto/rand"
+ "encoding/hex"
+ "errors"
+)
+
+// Opcodes
+const (
+ OpLoginRequest uint16 = 0x0001
+ OpLoginReply uint16 = 0x0002
+ OpWorldListRequest uint16 = 0x0003
+ OpWorldListReply uint16 = 0x0004
+ OpCharacterListRequest uint16 = 0x0005
+ OpCharacterListReply uint16 = 0x0006
+ OpCreateCharacter uint16 = 0x0007
+ OpCreateCharacterReply uint16 = 0x0008
+ OpDeleteCharacter uint16 = 0x0009
+ OpDeleteCharacterReply uint16 = 0x000A
+ OpPlayCharacter uint16 = 0x000B
+ OpPlayCharacterReply uint16 = 0x000C
+ OpError uint16 = 0xFFFF
+)
+
+var ErrIncompletePacket = errors.New("incomplete packet")
+
+type EQ2Packet struct {
+ Opcode uint16
+ Size uint16
+ Data []byte
+}
+
+type LoginRequest struct {
+ Username string `json:"username"`
+ Password string `json:"password"`
+ Version int16 `json:"version"`
+}
+
+type LoginResponse struct {
+ Response uint8 `json:"response"`
+ AccountID uint32 `json:"account_id"`
+ SubLevel uint32 `json:"sub_level"`
+ RaceFlag uint32 `json:"race_flag"`
+ ClassFlag uint32 `json:"class_flag"`
+ Username string `json:"username"`
+ Unknown5 uint32 `json:"unknown5"`
+ CitiesFlag uint8 `json:"cities_flag"`
+ Reason string `json:"reason,omitempty"`
+}
+
+type WorldInfo struct {
+ ID uint32 `json:"id"`
+ Name string `json:"name"`
+ Status uint8 `json:"status"`
+ Players uint32 `json:"players"`
+ MaxPlayers uint32 `json:"max_players"`
+ Address string `json:"address"`
+ Port uint16 `json:"port"`
+ Locked bool `json:"locked"`
+}
+
+type WorldListResponse struct {
+ NumWorlds uint32 `json:"num_worlds"`
+ Worlds []*WorldInfo `json:"worlds"`
+}
+
+type Character struct {
+ ID uint32 `json:"id"`
+ Name string `json:"name"`
+ Race uint8 `json:"race"`
+ Class uint8 `json:"class"`
+ Gender uint8 `json:"gender"`
+ Level uint8 `json:"level"`
+ Zone string `json:"zone"`
+ ServerID uint32 `json:"server_id"`
+ Created int64 `json:"created"`
+ LastLogin int64 `json:"last_login"`
+}
+
+type CharacterListResponse struct {
+ NumCharacters uint32 `json:"num_characters"`
+ Characters []*Character `json:"characters"`
+ AccountID uint32 `json:"account_id"`
+ MaxChars uint32 `json:"max_chars"`
+}
+
+type CreateCharacterRequest struct {
+ Name string `json:"name"`
+ Race uint8 `json:"race"`
+ Class uint8 `json:"class"`
+ Gender uint8 `json:"gender"`
+ Deity uint8 `json:"deity"`
+ BodySize float32 `json:"body_size"`
+ BodyAge float32 `json:"body_age"`
+ ServerID uint32 `json:"server_id"`
+}
+
+type CreateCharacterResponse struct {
+ Success bool `json:"success"`
+ CharacterID uint32 `json:"character_id"`
+ Name string `json:"name"`
+ Error string `json:"error,omitempty"`
+}
+
+type DeleteCharacterRequest struct {
+ CharacterID uint32 `json:"character_id"`
+ ServerID uint32 `json:"server_id"`
+}
+
+type DeleteCharacterResponse struct {
+ Success bool `json:"success"`
+ CharacterID uint32 `json:"character_id"`
+ Error string `json:"error,omitempty"`
+}
+
+type PlayCharacterRequest struct {
+ CharacterID uint32 `json:"character_id"`
+ ServerID uint32 `json:"server_id"`
+}
+
+type PlayCharacterResponse struct {
+ Success bool `json:"success"`
+ ServerIP string `json:"server_ip"`
+ ServerPort uint16 `json:"server_port"`
+ SessionKey string `json:"session_key"`
+ Error string `json:"error,omitempty"`
+}
+
+type OpcodeManager struct {
+ opcodes map[string]uint16
+}
+
+func (om *OpcodeManager) GetOpcode(name string) (uint16, bool) {
+ opcode, exists := om.opcodes[name]
+ return opcode, exists
+}
+
+func generateSessionKey() string {
+ bytes := make([]byte, 16)
+ rand.Read(bytes)
+ return hex.EncodeToString(bytes)
+}
diff --git a/cmd/loginServer/tcp.go b/cmd/loginServer/tcp.go
new file mode 100644
index 0000000..b14f2a9
--- /dev/null
+++ b/cmd/loginServer/tcp.go
@@ -0,0 +1,300 @@
+package main
+
+import (
+ "encoding/binary"
+ "log"
+ "time"
+
+ "github.com/goccy/go-json"
+ "github.com/panjf2000/gnet/v2"
+)
+
+type TCPServer struct {
+ gnet.BuiltinEventEngine
+ server *LoginServer
+ clients *ClientManager
+ worlds *WorldManager
+}
+
+func (s *TCPServer) OnBoot(eng gnet.Engine) gnet.Action {
+ log.Printf("TCP server started on %s", eng.Addr())
+ return gnet.None
+}
+
+func (s *TCPServer) OnOpen(c gnet.Conn) ([]byte, gnet.Action) {
+ addr := c.RemoteAddr().String()
+ log.Printf("New connection from %s", addr)
+
+ client := &Client{
+ conn: c,
+ address: addr,
+ connected: time.Now(),
+ state: StateConnected,
+ version: 0,
+ }
+
+ s.clients.AddClient(c.Fd(), client)
+ return nil, gnet.None
+}
+
+func (s *TCPServer) OnClose(c gnet.Conn, err error) gnet.Action {
+ s.clients.RemoveClient(c.Fd())
+ if err != nil {
+ log.Printf("Connection closed with error: %v", err)
+ }
+ return gnet.None
+}
+
+func (s *TCPServer) OnTraffic(c gnet.Conn) gnet.Action {
+ client := s.clients.GetClient(c.Fd())
+ if client == nil {
+ return gnet.Close
+ }
+
+ for {
+ packet, err := s.readPacket(c)
+ if err != nil {
+ if err != ErrIncompletePacket {
+ log.Printf("Error reading packet: %v", err)
+ return gnet.Close
+ }
+ break
+ }
+
+ if err := s.handlePacket(client, packet); err != nil {
+ log.Printf("Error handling packet: %v", err)
+ return gnet.Close
+ }
+ }
+
+ return gnet.None
+}
+
+func (s *TCPServer) readPacket(c gnet.Conn) (*EQ2Packet, error) {
+ // Read packet header (2 bytes length + 2 bytes opcode)
+ if c.InboundBuffered() < 4 {
+ return nil, ErrIncompletePacket
+ }
+
+ header := make([]byte, 4)
+ if _, err := c.Peek(header); err != nil {
+ return nil, err
+ }
+
+ length := binary.LittleEndian.Uint16(header[0:2])
+ opcode := binary.LittleEndian.Uint16(header[2:4])
+
+ totalLength := int(length) + 4 // Add header size
+ if c.InboundBuffered() < totalLength {
+ return nil, ErrIncompletePacket
+ }
+
+ // Read complete packet
+ data := make([]byte, totalLength)
+ if _, err := c.Read(data); err != nil {
+ return nil, err
+ }
+
+ return &EQ2Packet{
+ Opcode: opcode,
+ Size: length,
+ Data: data[4:], // Skip header
+ }, nil
+}
+
+func (s *TCPServer) handlePacket(client *Client, packet *EQ2Packet) error {
+ switch packet.Opcode {
+ case OpLoginRequest:
+ return s.handleLoginRequest(client, packet)
+ case OpWorldListRequest:
+ return s.handleWorldListRequest(client, packet)
+ case OpCharacterListRequest:
+ return s.handleCharacterListRequest(client, packet)
+ case OpCreateCharacter:
+ return s.handleCreateCharacter(client, packet)
+ case OpDeleteCharacter:
+ return s.handleDeleteCharacter(client, packet)
+ case OpPlayCharacter:
+ return s.handlePlayCharacter(client, packet)
+ default:
+ log.Printf("Unknown opcode: 0x%04X", packet.Opcode)
+ }
+ return nil
+}
+
+func (s *TCPServer) handleLoginRequest(client *Client, packet *EQ2Packet) error {
+ var loginReq LoginRequest
+ if err := json.Unmarshal(packet.Data, &loginReq); err != nil {
+ return s.sendLoginDenied(client, "Invalid login data")
+ }
+
+ account, err := s.server.db.Get(s.server.ctx)
+ if err != nil {
+ return s.sendLoginDenied(client, "Database error")
+ }
+ defer s.server.db.Put(account)
+
+ // Authenticate user
+ authenticated, accountID, err := authenticateUser(account, loginReq.Username, loginReq.Password, s.server.config.Game.AllowAccountCreation)
+ if err != nil {
+ return s.sendLoginDenied(client, "Authentication failed")
+ }
+
+ if !authenticated {
+ return s.sendLoginDenied(client, "Invalid credentials")
+ }
+
+ client.accountID = accountID
+ client.accountName = loginReq.Username
+ client.state = StateAuthenticated
+ client.version = loginReq.Version
+
+ return s.sendLoginAccepted(client)
+}
+
+func (s *TCPServer) sendLoginAccepted(client *Client) error {
+ response := LoginResponse{
+ Response: 1,
+ AccountID: client.accountID,
+ SubLevel: s.server.config.Game.DefaultSubscriptionLevel,
+ RaceFlag: s.server.config.Game.EnabledRaces,
+ ClassFlag: 0x7FFFFFE,
+ Username: client.accountName,
+ Unknown5: s.server.config.Game.ExpansionFlag,
+ CitiesFlag: s.server.config.Game.CitiesFlag,
+ }
+
+ return s.sendPacket(client, OpLoginReply, &response)
+}
+
+func (s *TCPServer) sendLoginDenied(client *Client, reason string) error {
+ response := LoginResponse{
+ Response: 0,
+ Reason: reason,
+ }
+ return s.sendPacket(client, OpLoginReply, &response)
+}
+
+func (s *TCPServer) handleWorldListRequest(client *Client, packet *EQ2Packet) error {
+ if client.state != StateAuthenticated {
+ return s.sendError(client, "Not authenticated")
+ }
+
+ worlds := s.worlds.GetWorldList()
+ worldList := WorldListResponse{
+ NumWorlds: uint32(len(worlds)),
+ Worlds: worlds,
+ }
+
+ return s.sendPacket(client, OpWorldListReply, &worldList)
+}
+
+func (s *TCPServer) handleCharacterListRequest(client *Client, packet *EQ2Packet) error {
+ if client.state != StateAuthenticated {
+ return s.sendError(client, "Not authenticated")
+ }
+
+ conn := s.server.db.Get(s.server.ctx)
+ defer s.server.db.Put(conn)
+
+ characters, err := getCharacterList(conn, client.accountID)
+ if err != nil {
+ return s.sendError(client, "Failed to load characters")
+ }
+
+ charList := CharacterListResponse{
+ NumCharacters: uint32(len(characters)),
+ Characters: characters,
+ AccountID: client.accountID,
+ MaxChars: uint32(s.server.config.Game.MaxCharactersPerAccount),
+ }
+
+ return s.sendPacket(client, OpCharacterListReply, &charList)
+}
+
+func (s *TCPServer) sendPacket(client *Client, opcode uint16, data any) error {
+ jsonData, err := json.Marshal(data)
+ if err != nil {
+ return err
+ }
+
+ packet := make([]byte, 4+len(jsonData))
+ binary.LittleEndian.PutUint16(packet[0:2], uint16(len(jsonData)))
+ binary.LittleEndian.PutUint16(packet[2:4], opcode)
+ copy(packet[4:], jsonData)
+
+ _, err = client.conn.Write(packet)
+ return err
+}
+
+func (s *TCPServer) sendError(client *Client, message string) error {
+ return s.sendPacket(client, OpError, map[string]string{"error": message})
+}
+
+func (s *TCPServer) handleCreateCharacter(client *Client, packet *EQ2Packet) error {
+ var createReq CreateCharacterRequest
+ if err := json.Unmarshal(packet.Data, &createReq); err != nil {
+ return s.sendError(client, "Invalid character data")
+ }
+
+ conn := s.server.db.Get(s.server.ctx)
+ defer s.server.db.Put(conn)
+
+ charID, err := createCharacter(conn, client.accountID, &createReq)
+ if err != nil {
+ return s.sendError(client, "Failed to create character")
+ }
+
+ response := CreateCharacterResponse{
+ Success: true,
+ CharacterID: charID,
+ Name: createReq.Name,
+ }
+
+ return s.sendPacket(client, OpCreateCharacterReply, &response)
+}
+
+func (s *TCPServer) handleDeleteCharacter(client *Client, packet *EQ2Packet) error {
+ var deleteReq DeleteCharacterRequest
+ if err := json.Unmarshal(packet.Data, &deleteReq); err != nil {
+ return s.sendError(client, "Invalid delete request")
+ }
+
+ conn := s.server.db.Get(s.server.ctx)
+ defer s.server.db.Put(conn)
+
+ if err := deleteCharacter(conn, client.accountID, deleteReq.CharacterID); err != nil {
+ return s.sendError(client, "Failed to delete character")
+ }
+
+ response := DeleteCharacterResponse{
+ Success: true,
+ CharacterID: deleteReq.CharacterID,
+ }
+
+ return s.sendPacket(client, OpDeleteCharacterReply, &response)
+}
+
+func (s *TCPServer) handlePlayCharacter(client *Client, packet *EQ2Packet) error {
+ var playReq PlayCharacterRequest
+ if err := json.Unmarshal(packet.Data, &playReq); err != nil {
+ return s.sendError(client, "Invalid play request")
+ }
+
+ world := s.worlds.GetWorld(playReq.ServerID)
+ if world == nil {
+ return s.sendError(client, "World server not available")
+ }
+
+ // Generate session key and send to world server
+ sessionKey := generateSessionKey()
+
+ response := PlayCharacterResponse{
+ Success: true,
+ ServerIP: world.Address,
+ ServerPort: world.Port,
+ SessionKey: sessionKey,
+ }
+
+ return s.sendPacket(client, OpPlayCharacterReply, &response)
+}
diff --git a/cmd/loginServer/web.go b/cmd/loginServer/web.go
new file mode 100644
index 0000000..a695eb2
--- /dev/null
+++ b/cmd/loginServer/web.go
@@ -0,0 +1,263 @@
+package main
+
+import (
+ "encoding/base64"
+ "strings"
+ "time"
+
+ "github.com/goccy/go-json"
+ "github.com/valyala/fasthttp"
+)
+
+type WebRouter struct {
+ server *LoginServer
+}
+
+func (wr *WebRouter) Handler(ctx *fasthttp.RequestCtx) {
+ path := string(ctx.Path())
+
+ // Basic auth check
+ if !wr.checkAuth(ctx) {
+ ctx.Response.Header.Set("WWW-Authenticate", `Basic realm="EQ2 Login Server"`)
+ ctx.SetStatusCode(fasthttp.StatusUnauthorized)
+ ctx.SetBodyString("Unauthorized")
+ return
+ }
+
+ switch path {
+ case "/":
+ wr.handleStatus(ctx)
+ case "/status":
+ wr.handleStatus(ctx)
+ case "/worlds":
+ wr.handleWorlds(ctx)
+ case "/clients":
+ wr.handleClients(ctx)
+ case "/stats":
+ wr.handleStats(ctx)
+ default:
+ ctx.SetStatusCode(fasthttp.StatusNotFound)
+ ctx.SetBodyString("Not Found")
+ }
+}
+
+func (wr *WebRouter) checkAuth(ctx *fasthttp.RequestCtx) bool {
+ if wr.server.config.Web.Username == "" {
+ return true // No auth required
+ }
+
+ auth := ctx.Request.Header.Peek("Authorization")
+ if len(auth) == 0 {
+ return false
+ }
+
+ if !strings.HasPrefix(string(auth), "Basic ") {
+ return false
+ }
+
+ encoded := string(auth[6:])
+ decoded, err := base64.StdEncoding.DecodeString(encoded)
+ if err != nil {
+ return false
+ }
+
+ parts := strings.SplitN(string(decoded), ":", 2)
+ if len(parts) != 2 {
+ return false
+ }
+
+ return parts[0] == wr.server.config.Web.Username &&
+ parts[1] == wr.server.config.Web.Password
+}
+
+func (wr *WebRouter) handleStatus(ctx *fasthttp.RequestCtx) {
+ ctx.Response.Header.Set("Content-Type", "application/json")
+
+ totalClients, authClients := wr.server.clients.GetStats()
+ totalWorlds, onlineWorlds := wr.server.worlds.GetStats()
+
+ conn := wr.server.db.Get(wr.server.ctx)
+ defer wr.server.db.Put(conn)
+
+ totalAccounts, totalChars, _ := getAccountStats(conn)
+
+ status := map[string]any{
+ "server_name": "EQ2 Login Server",
+ "status": "online",
+ "uptime": time.Since(wr.server.startTime).Seconds(),
+ "uptime_string": formatDuration(time.Since(wr.server.startTime)),
+ "version": "1.0.0-go",
+ "clients": map[string]int{
+ "total": totalClients,
+ "authenticated": authClients,
+ },
+ "worlds": map[string]int{
+ "total": totalWorlds,
+ "online": onlineWorlds,
+ },
+ "database": map[string]int{
+ "accounts": totalAccounts,
+ "characters": totalChars,
+ },
+ "timestamp": time.Now().Unix(),
+ }
+
+ data, _ := json.Marshal(status)
+ ctx.SetBody(data)
+}
+
+func (wr *WebRouter) handleWorlds(ctx *fasthttp.RequestCtx) {
+ ctx.Response.Header.Set("Content-Type", "application/json")
+
+ worlds := wr.server.worlds.GetWorldList()
+
+ response := map[string]any{
+ "worlds": worlds,
+ "count": len(worlds),
+ }
+
+ data, _ := json.Marshal(response)
+ ctx.SetBody(data)
+}
+
+func (wr *WebRouter) handleClients(ctx *fasthttp.RequestCtx) {
+ ctx.Response.Header.Set("Content-Type", "application/json")
+
+ wr.server.clients.mu.RLock()
+ defer wr.server.clients.mu.RUnlock()
+
+ clients := make([]map[string]any, 0, len(wr.server.clients.clients))
+
+ for _, client := range wr.server.clients.clients {
+ clients = append(clients, map[string]any{
+ "address": client.address,
+ "connected": client.connected.Unix(),
+ "state": stateToString(client.state),
+ "account_name": client.accountName,
+ "version": client.version,
+ })
+ }
+
+ response := map[string]any{
+ "clients": clients,
+ "count": len(clients),
+ }
+
+ data, _ := json.Marshal(response)
+ ctx.SetBody(data)
+}
+
+func (wr *WebRouter) handleStats(ctx *fasthttp.RequestCtx) {
+ ctx.Response.Header.Set("Content-Type", "application/json")
+
+ // Get comprehensive stats
+ totalClients, authClients := wr.server.clients.GetStats()
+ totalWorlds, onlineWorlds := wr.server.worlds.GetStats()
+
+ conn := wr.server.db.Get(wr.server.ctx)
+ defer wr.server.db.Put(conn)
+
+ totalAccounts, totalChars, _ := getAccountStats(conn)
+
+ stats := map[string]any{
+ "server": map[string]any{
+ "uptime": time.Since(wr.server.startTime).Seconds(),
+ "start_time": wr.server.startTime.Unix(),
+ "version": "1.0.0-go",
+ },
+ "clients": map[string]any{
+ "total": totalClients,
+ "authenticated": authClients,
+ "guest": totalClients - authClients,
+ },
+ "worlds": map[string]any{
+ "total": totalWorlds,
+ "online": onlineWorlds,
+ "offline": totalWorlds - onlineWorlds,
+ },
+ "database": map[string]any{
+ "accounts": totalAccounts,
+ "characters": totalChars,
+ "avg_chars": float64(totalChars) / float64(max(totalAccounts, 1)),
+ },
+ "config": map[string]any{
+ "max_clients": wr.server.config.Server.MaxClients,
+ "max_world_servers": wr.server.config.Server.MaxWorldServers,
+ "allow_account_creation": wr.server.config.Game.AllowAccountCreation,
+ "max_chars_per_account": wr.server.config.Game.MaxCharactersPerAccount,
+ },
+ }
+
+ data, _ := json.Marshal(stats)
+ ctx.SetBody(data)
+}
+
+func stateToString(state ClientState) string {
+ switch state {
+ case StateConnected:
+ return "connected"
+ case StateAuthenticated:
+ return "authenticated"
+ case StateCharacterSelect:
+ return "character_select"
+ case StateDisconnected:
+ return "disconnected"
+ default:
+ return "unknown"
+ }
+}
+
+func formatDuration(d time.Duration) string {
+ days := int(d.Hours()) / 24
+ hours := int(d.Hours()) % 24
+ minutes := int(d.Minutes()) % 60
+
+ if days > 0 {
+ return formatTime(days, "day") + ", " + formatTime(hours, "hour") + ", " + formatTime(minutes, "minute")
+ }
+ if hours > 0 {
+ return formatTime(hours, "hour") + ", " + formatTime(minutes, "minute")
+ }
+ return formatTime(minutes, "minute")
+}
+
+func formatTime(value int, unit string) string {
+ if value == 1 {
+ return "1 " + unit
+ }
+ return string(rune(value)) + " " + unit + "s"
+}
+
+func max(a, b int) int {
+ if a > b {
+ return a
+ }
+ return b
+}
+
+// Example login_config.json file
+const ExampleConfig = `{
+ "server": {
+ "port": 5998,
+ "max_clients": 1000,
+ "max_world_servers": 10
+ },
+ "database": {
+ "path": "login.db"
+ },
+ "web": {
+ "enabled": true,
+ "address": "0.0.0.0",
+ "port": 8080,
+ "username": "admin",
+ "password": "password"
+ },
+ "game": {
+ "allow_account_creation": true,
+ "expansion_flag": 32767,
+ "cities_flag": 255,
+ "default_subscription_level": 4294967295,
+ "enabled_races": 65535,
+ "max_characters_per_account": 7
+ }
+}`
diff --git a/cmd/loginServer/worlds.go b/cmd/loginServer/worlds.go
new file mode 100644
index 0000000..63b66fc
--- /dev/null
+++ b/cmd/loginServer/worlds.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+ "sync"
+ "time"
+
+ "github.com/panjf2000/gnet/v2"
+ "zombiezen.com/go/sqlite/sqlitex"
+)
+
+type WorldServer struct {
+ ID uint32
+ Name string
+ Address string
+ Port uint16
+ Status uint8
+ Players uint32
+ MaxPlayers uint32
+ Locked bool
+ LastUpdate time.Time
+ conn gnet.Conn
+ mu sync.RWMutex
+}
+
+type WorldManager struct {
+ worlds map[uint32]*WorldServer
+ db *sqlitex.Pool
+ mu sync.RWMutex
+}
+
+func NewWorldManager(db *sqlitex.Pool) *WorldManager {
+ return &WorldManager{
+ worlds: make(map[uint32]*WorldServer),
+ db: db,
+ }
+}
+
+func (wm *WorldManager) AddWorld(world *WorldServer) {
+ wm.mu.Lock()
+ defer wm.mu.Unlock()
+ wm.worlds[world.ID] = world
+}
+
+func (wm *WorldManager) RemoveWorld(id uint32) {
+ wm.mu.Lock()
+ defer wm.mu.Unlock()
+ delete(wm.worlds, id)
+}
+
+func (wm *WorldManager) GetWorld(id uint32) *WorldServer {
+ wm.mu.RLock()
+ defer wm.mu.RUnlock()
+ return wm.worlds[id]
+}
+
+func (wm *WorldManager) GetWorldList() []*WorldInfo {
+ wm.mu.RLock()
+ defer wm.mu.RUnlock()
+
+ worlds := make([]*WorldInfo, 0, len(wm.worlds))
+ for _, world := range wm.worlds {
+ worlds = append(worlds, &WorldInfo{
+ ID: world.ID,
+ Name: world.Name,
+ Status: world.Status,
+ Players: world.Players,
+ MaxPlayers: world.MaxPlayers,
+ Address: world.Address,
+ Port: world.Port,
+ Locked: world.Locked,
+ })
+ }
+
+ return worlds
+}
+
+func (wm *WorldManager) UpdateStats() {
+ wm.mu.Lock()
+ defer wm.mu.Unlock()
+
+ cutoff := time.Now().Add(-5 * time.Minute)
+ for _, world := range wm.worlds {
+ if world.LastUpdate.Before(cutoff) {
+ world.Status = 0 // Offline
+ }
+ }
+}
+
+func (wm *WorldManager) GetStats() (int, int) {
+ wm.mu.RLock()
+ defer wm.mu.RUnlock()
+
+ total := len(wm.worlds)
+ online := 0
+
+ for _, world := range wm.worlds {
+ if world.Status == 1 {
+ online++
+ }
+ }
+
+ return total, online
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..2170441
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,29 @@
+module eq2emu
+
+go 1.24.1
+
+require (
+ github.com/andybalholm/brotli v1.2.0 // indirect
+ github.com/dustin/go-humanize v1.0.1 // indirect
+ github.com/goccy/go-json v0.10.5 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/klauspost/compress v1.18.0 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/ncruces/go-strftime v0.1.9 // indirect
+ github.com/panjf2000/ants/v2 v2.11.3 // indirect
+ github.com/panjf2000/gnet/v2 v2.9.1 // indirect
+ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ github.com/valyala/fasthttp v1.63.0 // indirect
+ go.uber.org/multierr v1.11.0 // indirect
+ go.uber.org/zap v1.27.0 // indirect
+ golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
+ golang.org/x/sync v0.15.0 // indirect
+ golang.org/x/sys v0.33.0 // indirect
+ gopkg.in/natefinch/lumberjack.v2 v2.2.1 // 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
new file mode 100644
index 0000000..90e8082
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,47 @@
+github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ=
+github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY=
+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/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4=
+github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
+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/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
+github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
+github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
+github.com/panjf2000/ants/v2 v2.11.3 h1:AfI0ngBoXJmYOpDh9m516vjqoUu2sLrIVgppI9TZVpg=
+github.com/panjf2000/ants/v2 v2.11.3/go.mod h1:8u92CYMUc6gyvTIw8Ru7Mt7+/ESnJahz5EVtqfrilek=
+github.com/panjf2000/gnet/v2 v2.9.1 h1:bKewICy/0xnQ9PMzNaswpe/Ah14w1TrRk91LHTcbIlA=
+github.com/panjf2000/gnet/v2 v2.9.1/go.mod h1:WQTxDWYuQ/hz3eccH0FN32IVuvZ19HewEWx0l62fx7E=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
+github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.63.0 h1:DisIL8OjB7ul2d7cBaMRcKTQDYnrGy56R4FCiuDP0Ns=
+github.com/valyala/fasthttp v1.63.0/go.mod h1:REc4IeW+cAEyLrRPa5A81MIjvz0QE1laoTX2EaPHKJM=
+go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
+go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
+go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
+go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
+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/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8=
+golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
+golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
+gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
+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/sqlite v1.37.1 h1:EgHJK/FPoqC+q2YBXg7fUmES37pCHFc97sI7zSayBEs=
+modernc.org/sqlite v1.37.1/go.mod h1:XwdRtsE1MpiBcL54+MbKcaDvcuej+IYSMfLN6gSKV8g=
+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/opcodes/game.go b/internal/opcodes/game.go
new file mode 100644
index 0000000..6e01e30
--- /dev/null
+++ b/internal/opcodes/game.go
@@ -0,0 +1,469 @@
+package opcodes
+
+// Game application opcodes from emu_oplist.h
+const (
+ OP_Unknown = 0
+
+ // Core game messages
+ OP_ESInitMsg = iota + 100
+ OP_ESReadyForClientsMsg
+ OP_CreateZoneInstanceMsg
+ OP_ZoneInstanceCreateReplyMsg
+ OP_ZoneInstanceDestroyedMsg
+ OP_ExpectClientAsCharacterRequest
+ OP_ExpectClientAsCharacterReplyMsg
+ OP_ZoneInfoMsg
+ OP_DoneLoadingZoneResourcesMsg
+ OP_DoneSendingInitialEntitiesMsg
+ OP_DoneLoadingEntityResourcesMsg
+ OP_DoneLoadingUIResourcesMsg
+ OP_PredictionUpdateMsg
+ OP_RemoteCmdMsg
+ OP_SetRemoteCmdsMsg
+ OP_GameWorldTimeMsg
+ OP_MOTDMsg
+ OP_ZoneMOTDMsg
+
+ // Guild recruiting
+ OP_GuildRecruitingMemberInfo
+ OP_GuildRecruiting
+ OP_GuildRecruitingDetails
+ OP_GuildRecruitingImage
+
+ // Avatar management
+ OP_AvatarCreatedMsg
+ OP_AvatarDestroyedMsg
+ OP_RequestCampMsg
+ OP_MapRequest
+ OP_CampStartedMsg
+ OP_CampAbortedMsg
+
+ // Communication
+ OP_WhoQueryRequestMsg
+ OP_WhoQueryReplyMsg
+ OP_MonitorReplyMsg
+ OP_MonitorCharacterListMsg
+ OP_MonitorCharacterListRequestMsg
+ OP_ClientCmdMsg
+ OP_Lottery
+ OP_DispatchClientCmdMsg
+ OP_DispatchESMsg
+
+ // Updates
+ OP_UpdateTargetMsg
+ OP_UpdateOpportunityMsg
+ OP_UpdateTargetLocMsg
+ OP_UpdateSpellBookMsg
+ OP_UpdateRecipeBookMsg
+ OP_RequestRecipeDetailsMsg
+ OP_RecipeDetailsMsg
+ OP_UpdateSkillBookMsg
+ OP_UpdateSkillsMsg
+
+ // Zone management
+ OP_ChangeZoneMsg
+ OP_ClientTeleportRequestMsg
+ OP_TeleportWithinZoneMsg
+ OP_TeleportWithinZoneNoReloadMsg
+ OP_MigrateClientToZoneRequestMsg
+ OP_MigrateClientToZoneReplyMsg
+ OP_ReadyToZoneMsg
+
+ // Group management
+ OP_RemoveClientFromGroupMsg
+ OP_RemoveGroupFromGroupMsg
+ OP_MakeGroupLeaderMsg
+ OP_GroupCreatedMsg
+ OP_GroupDestroyedMsg
+ OP_GroupMemberAddedMsg
+ OP_GroupMemberRemovedMsg
+ OP_GroupRemovedFromGroupMsg
+ OP_GroupLeaderChangedMsg
+ OP_GroupSettingsChangedMsg
+
+ // Status and control
+ OP_SendLatestRequestMsg
+ OP_ClearDataMsg
+ OP_SetSocialMsg
+ OP_ESStatusMsg
+ OP_ESZoneInstanceStatusMsg
+ OP_ZonesStatusRequestMsg
+ OP_ZonesStatusMsg
+ OP_ESWeatherRequestMsg
+ OP_ESWeatherRequestEndMsg
+
+ // Loot and items
+ OP_LootItemsRequestMsg
+ OP_StoppedLootingMsg
+
+ // Character actions
+ OP_SitMsg
+ OP_StandMsg
+ OP_SatMsg
+ OP_StoodMsg
+
+ // UI and interface
+ OP_DefaultGroupOptionsRequestMsg
+ OP_DefaultGroupOptionsMsg
+ OP_GroupOptionsMsg
+ OP_DisplayGroupOptionsScreenMsg
+ OP_DisplayInnVisitScreenMsg
+ OP_DumpSchedulerMsg
+ OP_RequestHelpRepathMsg
+ OP_UpdateMotdMsg
+ OP_RequestTargetLocMsg
+
+ // Effects and actions
+ OP_PerformPlayerKnockbackMsg
+ OP_PerformCameraShakeMsg
+ OP_PopulateSkillMapsMsg
+ OP_CancelledFeignMsg
+ OP_SignalMsg
+
+ // Skills and crafting
+ OP_SkillInfoRequest
+ OP_SkillInfoResponse
+ OP_ShowCreateFromRecipeUIMsg
+ OP_CancelCreateFromRecipeMsg
+ OP_BeginItemCreationMsg
+ OP_StopItemCreationMsg
+ OP_ShowItemCreationProcessUIMsg
+ OP_UpdateItemCreationProcessUIMsg
+ OP_DisplayTSEventReactionMsg
+ OP_ShowRecipeBookMsg
+
+ // Knowledge and help
+ OP_KnowledgebaseRequestMsg
+ OP_KnowledgebaseResponseMsg
+
+ // Customer service
+ OP_CSTicketHeaderRequestMsg
+ OP_CSTicketInfoMsg
+ OP_CSTicketCommentRequestMsg
+ OP_CSTicketCommentResponseMsg
+ OP_CSTicketCreateMsg
+ OP_CSTicketAddCommentMsg
+ OP_CSTicketDeleteMsg
+ OP_CSTicketChangeNotificationMsg
+
+ // World data
+ OP_WorldDataUpdateMsg
+ OP_WorldDataChangeMsg
+ OP_KnownLanguagesMsg
+
+ // Client management
+ OP_ClientTeleportToLocationMsg
+ OP_UpdateClientPredFlagsMsg
+ OP_ChangeServerControlFlagMsg
+ OP_CSToolsRequestMsg
+ OP_CSToolsResponseMsg
+
+ // Transport
+ OP_CreateBoatTransportsMsg
+ OP_PositionBoatTransportMsg
+ OP_MigrateBoatTransportMsg
+ OP_MigrateBoatTransportReplyMsg
+
+ // Debug and examination
+ OP_DisplayDebugNLLPointsMsg
+ OP_ExamineInfoRequestMsg
+
+ // UI management
+ OP_QuickbarInitMsg
+ OP_QuickbarUpdateMsg
+ OP_MacroInitMsg
+ OP_MacroUpdateMsg
+ OP_QuestionnaireMsg
+
+ // Character progression
+ OP_LevelChangedMsg
+ OP_SpellGainedMsg
+ OP_EncounterBrokenMsg
+ OP_OnscreenMsgMsg
+ OP_DisplayWarningMsg
+
+ // Guild management
+ OP_ModifyGuildMsg
+ OP_GuildEventMsg
+ OP_GuildEventAddMsg
+ OP_GuildEventActionMsg
+ OP_GuildEventListMsg
+ OP_RequestGuildEventDetailsMsg
+ OP_GuildEventDetailsMsg
+ OP_RequestGuildBankEventDetailsMsg
+ OP_GuildBankUpdateMsg
+ OP_RewardPackMsg
+ OP_RenameGuildMsg
+
+ // Social features
+ OP_ZoneToFriendRequestMsg
+ OP_ZoneToFriendReplyMsg
+ OP_WaypointRequestMsg
+ OP_WaypointReplyMsg
+ OP_WaypointSelectMsg
+ OP_WaypointUpdateMsg
+ OP_CharNameChangedMsg
+
+ // Travel
+ OP_ShowZoneTeleporterDestinations
+ OP_SelectZoneTeleporterDestination
+ OP_ReloadLocalizedTxtMsg
+
+ // Guild membership
+ OP_RequestGuildMembershipMsg
+ OP_GuildMembershipResponseMsg
+ OP_LeaveGuildNotifyMsg
+ OP_JoinGuildNotifyMsg
+ OP_RequestGuildInfoMsg
+ OP_GuildBankEventListMsg
+
+ // Character info
+ OP_AvatarUpdateMsg
+ OP_BioUpdateMsg
+ OP_InspectPlayerMsg
+
+ // Server management
+ OP_CsCategoryRequestMsg
+ OP_CsCategoryResponseMsg
+ OP_KnowledgeWindowSlotMappingMsg
+
+ // Status updates
+ OP_LFGUpdateMsg
+ OP_AFKUpdateMsg
+ OP_AnonUpdateMsg
+ OP_UpdateActivePublicZonesMsg
+ OP_UnknownNpcMsg
+ OP_PromoFlagsDetailsMsg
+
+ // Trading and consignment
+ OP_ConsignViewCreateMsg
+ OP_ConsignViewGetPageMsg
+ OP_ConsignViewReleaseMsg
+ OP_UpdateDebugRadiiMsg
+ OP_ConsignRemoveItemsMsg
+ OP_ReportMsg
+ OP_UpdateRaidMsg
+ OP_ConsignViewSortMsg
+
+ // Character features
+ OP_TitleUpdateMsg
+ OP_FlightPathsMsg
+ OP_ClientFellMsg
+ OP_ClientInDeathRegionMsg
+ OP_CampClientMsg
+
+ // Customer service tools
+ OP_GetAvatarAccessRequestForCSTools
+ OP_CSToolAccessResponseMsg
+ OP_DeleteGuildMsg
+
+ // Tracking
+ OP_TrackingUpdateMsg
+ OP_BeginTrackingMsg
+ OP_StopTrackingMsg
+ OP_AdvancementRequestMsg
+
+ // Map data
+ OP_MapFogDataInitMsg
+ OP_MapFogDataUpdateMsg
+ OP_CloseGroupInviteWindowMsg
+ OP_UpdateGroupMemberDataMsg
+ OP_WorldPingMsg
+ OP_MoveLogUpdateMsg
+ OP_OfferQuestMsg
+
+ // Mail system
+ OP_MailGetMessageMsg
+ OP_MailSendMessageMsg
+ OP_MailDeleteMessageMsg
+ OP_MailGetHeadersReplyMsg
+ OP_MailGetMessageReplyMsg
+ OP_MailSendMessageReplyMsg
+ OP_MailCommitSendMessageMsg
+ OP_MailSendSystemMessageMsg
+ OP_MailRemoveAttachFromMailMsg
+ OP_WorldShutdownUpdateMsg
+ OP_ClientIdleBeginMsg
+ OP_ClientIdleEndMsg
+ OP_DisplayMailScreenMsg
+ OP_NotifyApprenticeStoppedMentoring
+ OP_CorruptedClientMsg
+ OP_MailEventNotificationMsg
+ OP_RestartZoneMsg
+
+ // Character transfer
+ OP_CharTransferStartRequestMsg
+ OP_CharTransferStartReplyMsg
+ OP_CharTransferRequestMsg
+ OP_CharTransferReplyMsg
+ OP_CharTransferRollbackRequestMsg
+ OP_CharTransferCommitRequestMsg
+ OP_CharTransferRollbackReplyMsg
+ OP_CharTransferCommitReplyMsg
+ OP_GetCharacterSerializedRequestMsg
+ OP_GetCharacterSerializedReplyMsg
+ OP_CreateCharFromCBBRequestMsg
+ OP_CreateCharFromCBBReplyMsg
+
+ // Housing
+ OP_HousingDataChangedMsg
+ OP_HousingRestoreMsg
+
+ // Auction system
+ OP_AuctionItem
+ OP_AuctionItemReply
+ OP_AuctionCoin
+ OP_AuctionCoinReply
+ OP_AuctionCharacter
+ OP_AuctionCharacterReply
+ OP_AuctionCommitMsg
+ OP_AuctionAbortMsg
+ OP_CharTransferValidateRequestMsg
+ OP_CharTransferValidateReplyMsg
+ OP_CharacterLinkdeadMsg
+ OP_RaceRestrictionMsg
+ OP_SetInstanceDisplayNameMsg
+
+ // EQ command system
+ OP_EqHearChatCmd
+ OP_EqDisplayTextCmd
+ OP_EqCreateGhostCmd
+ OP_EqCreateWidgetCmd
+ OP_EqCreateSignWidgetCmd
+ OP_EqDestroyGhostCmd
+ OP_EqUpdateGhostCmd
+ OP_EqSetControlGhostCmd
+ OP_EqSetPOVGhostCmd
+ OP_EqHearCombatCmd
+ OP_EqHearSpellCastCmd
+ OP_EqHearSpellInterruptCmd
+ OP_EqHearSpellFizzleCmd
+ OP_EqHearConsiderCmd
+ OP_EqUpdateSubClassesCmd
+ OP_EqCreateListBoxCmd
+ OP_EqSetDebugPathPointsCmd
+ OP_EqCannedEmoteCmd
+ OP_EqStateCmd
+ OP_EqPlaySoundCmd
+ OP_EqPlaySound3DCmd
+ OP_EqPlayVoiceCmd
+ OP_EqHearDrowningCmd
+ OP_EqHearDeathCmd
+ OP_EqGroupMemberRemovedCmd
+ OP_EqHearChainEffectCmd
+ OP_EqReceiveOfferCmd
+ OP_EqInspectPCResultsCmd
+ OP_EqDrawablePathGraphCmd
+ OP_EqDialogOpenCmd
+ OP_EqDialogCloseCmd
+ OP_EqCollectionUpdateCmd
+ OP_EqCollectionFilterCmd
+ OP_EqCollectionItemCmd
+ OP_EqQuestJournalUpdateCmd
+ OP_EqQuestJournalReplyCmd
+ OP_EqQuestGroupCmd
+ OP_EqUpdateMerchantCmd
+ OP_EqUpdateStoreCmd
+ OP_EqUpdatePlayerTradeCmd
+ OP_EqHelpPathCmd
+ OP_EqHelpPathClearCmd
+ OP_EqUpdateBankCmd
+ OP_EqExamineInfoCmd
+ OP_EqCloseWindowCmd
+ OP_EqUpdateLootCmd
+ OP_EqJunctionListCmd
+ OP_EqShowDeathWindowCmd
+ OP_EqDisplaySpellFailCmd
+ OP_EqSpellCastStartCmd
+ OP_EqSpellCastEndCmd
+ OP_EqResurrectedCmd
+ OP_EqChoiceWinCmd
+ OP_EqSetDefaultVerbCmd
+ OP_EqInstructionWindowCmd
+ OP_EqInstructionWindowCloseCmd
+ OP_EqInstructionWindowGoalCmd
+ OP_EqInstructionWindowTaskCmd
+ OP_EqEnableGameEventCmd
+ OP_EqShowWindowCmd
+ OP_EqEnableWindowCmd
+ OP_EqFlashWindowCmd
+ OP_EqHearPlayFlavorCmd
+ OP_EqUpdateSignWidgetCmd
+ OP_EqDebugPVDCmd
+ OP_EqShowBookCmd
+ OP_EqQuestionnaireCmd
+ OP_EqGetProbsCmd
+ OP_EqHearHealCmd
+ OP_EqChatChannelUpdateCmd
+ OP_EqWhoChannelQueryReplyCmd
+ OP_EqAvailWorldChannelsCmd
+ OP_EqUpdateTargetCmd
+ OP_EqConsignmentItemsCmd
+ OP_EqStartBrokerCmd
+ OP_EqMapExplorationCmd
+ OP_EqStoreLogCmd
+ OP_EqSpellMoveToRangeAndRetryCmd
+ OP_EqUpdatePlayerMailCmd
+ OP_EqFactionUpdateCmd
+ OP_EQHearThreatCmd
+ OP_EqHearSpellNoLandCmd
+ OP_EQHearDispellCmd
+ OP_EqTargetItemCmd
+
+ // Arena and game features
+ OP_ArenaGameTypesMsg
+ OP_UpdateTitleCmd
+ OP_UpdatePositionMsg
+ OP_AttackNotAllowed
+ OP_AttackAllowed
+ OP_CancelSpellCast
+ OP_BadLanguageFilter
+ OP_DressingRoom
+ OP_TraitsList
+ OP_PointOfInterest
+ OP_AdventureList
+ OP_CharacterAchievements
+ OP_RecipeList
+ OP_BagOptions
+ OP_AchievementUpdateMsg
+ OP_PetOptions
+ OP_BrokerAddBag
+ OP_CharacterPet
+ OP_ClearForTakeOffMsg
+ OP_CharacterCurrency
+ OP_TradeskillList
+ OP_RecipeBook
+ OP_CharacterMerc
+ OP_AfterInvSpellUpdate
+ OP_CharacterCreatedDungeons
+ OP_CharacterHousingList
+ OP_HouseItemsList
+ OP_CharacterMounts
+ OP_LoadCalendarEvents
+ OP_LoadWelcomeWindow
+ OP_DungeonMakerItemRequest
+ OP_SysClient
+ OP_LFGGroupSearch
+ OP_MarketPlacePrices
+ OP_MarketFundsUpdate
+ OP_MarketAddFundsRequest
+ OP_ZoneBgInstanceList
+ OP_UIEvent
+ OP_Launchpad
+ OP_Weakness
+ OP_SavageBarInitMsg
+ OP_PetOptionsResponse
+ OP_CurrentPet
+ OP_JournalQuestStoryline
+ OP_DailyObjectives
+ OP_RecipeListUnknown
+ OP_ClearForLandingMsg
+ OP_LikeOption
+ OP_HeritageMsg
+ OP_OpenCharCust
+ OP_PaperdollImage
+ OP_ReadyForTakeOffMsg
+ OP_EarlyLandingRequestMsg
+ OP_SubmitCharCust
+ OP_DietyAbilityWindow
+)
diff --git a/internal/opcodes/login.go b/internal/opcodes/login.go
new file mode 100644
index 0000000..e3ed760
--- /dev/null
+++ b/internal/opcodes/login.go
@@ -0,0 +1,43 @@
+package opcodes
+
+// Login server opcodes from login_oplist.h
+const (
+ OP_LoginRequestMsg = iota + 1
+ OP_LoginByNumRequestMsg
+ OP_WSLoginRequestMsg
+ OP_ESLoginRequestMsg
+ OP_LoginReplyMsg
+ OP_WorldListMsg
+ OP_WorldStatusChangeMsg
+ OP_AllWSDescRequestMsg
+ OP_WSStatusReplyMsg
+ OP_AllCharactersDescRequestMsg
+ OP_AllCharactersDescReplyMsg
+ OP_CreateCharacterRequestMsg
+ OP_ReskinCharacterRequestMsg
+ OP_CreateCharacterReplyMsg
+ OP_WSCreateCharacterRequestMsg
+ OP_WSCreateCharacterReplyMsg
+ OP_DeleteCharacterRequestMsg
+ OP_DeleteCharacterReplyMsg
+ OP_PlayCharacterRequestMsg
+ OP_PlayCharacterReplyMsg
+ OP_ServerPlayCharacterRequestMsg
+ OP_ServerPlayCharacterReplyMsg
+ OP_KeymapLoadMsg
+ OP_KeymapNoneMsg
+ OP_KeymapDataMsg
+ OP_KeymapSaveMsg
+ OP_LSCheckAcctLockMsg
+ OP_WSAcctLockStatusMsg
+ OP_LsRequestClientCrashLogMsg
+ OP_LsClientBaselogReplyMsg
+ OP_LsClientCrashlogReplyMsg
+ OP_LsClientAlertlogReplyMsg
+ OP_LsClientVerifylogReplyMsg
+ OP_WSServerLockMsg
+ OP_WSServerHideMsg
+ OP_LSServerLockMsg
+ OP_UpdateCharacterSheetMsg
+ OP_UpdateInventoryMsg
+)
diff --git a/internal/opcodes/protocol.go b/internal/opcodes/protocol.go
new file mode 100644
index 0000000..3e32551
--- /dev/null
+++ b/internal/opcodes/protocol.go
@@ -0,0 +1,19 @@
+package opcodes
+
+// Protocol-level opcodes from op_codes.h
+const (
+ // Core protocol operations
+ OP_SessionRequest = 0x01
+ OP_SessionResponse = 0x02
+ OP_Combined = 0x03
+ OP_SessionDisconnect = 0x05
+ OP_KeepAlive = 0x06
+ OP_ServerKeyRequest = 0x07
+ OP_SessionStatResponse = 0x08
+ OP_Packet = 0x09
+ OP_Fragment = 0x0d
+ OP_OutOfOrderAck = 0x11
+ OP_Ack = 0x15
+ OP_AppCombined = 0x19
+ OP_OutOfSession = 0x1d
+)
diff --git a/internal/opcodes/server.go b/internal/opcodes/server.go
new file mode 100644
index 0000000..02f40de
--- /dev/null
+++ b/internal/opcodes/server.go
@@ -0,0 +1,111 @@
+package opcodes
+
+// Server communication opcodes from servertalk.h
+const (
+ // Core server operations
+ ServerOP_KeepAlive = 0x0001
+ ServerOP_ChannelMessage = 0x0002
+ ServerOP_SetZone = 0x0003
+ ServerOP_ShutdownAll = 0x0004
+ ServerOP_ZoneShutdown = 0x0005
+ ServerOP_ZoneBootup = 0x0006
+ ServerOP_ZoneStatus = 0x0007
+ ServerOP_SetConnectInfo = 0x0008
+ ServerOP_EmoteMessage = 0x0009
+ ServerOP_ClientList = 0x000A
+ ServerOP_Who = 0x000B
+ ServerOP_ZonePlayer = 0x000C
+ ServerOP_KickPlayer = 0x000D
+ ServerOP_RefreshGuild = 0x000E
+ ServerOP_GuildKickAll = 0x000F
+ ServerOP_GuildInvite = 0x0010
+ ServerOP_GuildRemove = 0x0011
+ ServerOP_GuildPromote = 0x0012
+ ServerOP_GuildDemote = 0x0013
+ ServerOP_GuildLeader = 0x0014
+ ServerOP_GuildGMSet = 0x0015
+ ServerOP_GuildGMSetRank = 0x0016
+ ServerOP_FlagUpdate = 0x0018
+ ServerOP_GMGoto = 0x0019
+ ServerOP_MultiLineMsg = 0x001A
+ ServerOP_Lock = 0x001B
+ ServerOP_Motd = 0x001C
+ ServerOP_Uptime = 0x001D
+ ServerOP_Petition = 0x001E
+ ServerOP_KillPlayer = 0x001F
+ ServerOP_UpdateGM = 0x0020
+ ServerOP_RezzPlayer = 0x0021
+ ServerOP_ZoneReboot = 0x0022
+ ServerOP_ZoneToZoneRequest = 0x0023
+ ServerOP_AcceptWorldEntrance = 0x0024
+ ServerOP_ZAAuth = 0x0025
+ ServerOP_ZAAuthFailed = 0x0026
+ ServerOP_ZoneIncClient = 0x0027
+ ServerOP_ClientListKA = 0x0028
+ ServerOP_ChangeWID = 0x0029
+ ServerOP_IPLookup = 0x002A
+ ServerOP_LockZone = 0x002B
+ ServerOP_ItemStatus = 0x002C
+ ServerOP_OOCMute = 0x002D
+ ServerOP_Revoke = 0x002E
+ ServerOP_GuildJoin = 0x002F
+ ServerOP_GroupIDReq = 0x0030
+ ServerOP_GroupIDReply = 0x0031
+ ServerOP_GroupLeave = 0x0032
+ ServerOP_RezzPlayerAccept = 0x0033
+ ServerOP_SpawnCondition = 0x0034
+ ServerOP_SpawnEvent = 0x0035
+
+ // Login server operations
+ ServerOP_LSInfo = 0x1000
+ ServerOP_LSStatus = 0x1001
+ ServerOP_LSClientAuth = 0x1002
+ ServerOP_LSFatalError = 0x1003
+ ServerOP_SystemwideMessage = 0x1005
+ ServerOP_ListWorlds = 0x1006
+ ServerOP_PeerConnect = 0x1007
+
+ // Packet management
+ ServerOP_EncapPacket = 0x2007
+ ServerOP_WorldListUpdate = 0x2008
+ ServerOP_WorldListRemove = 0x2009
+ ServerOP_TriggerWorldListRefresh = 0x200A
+ ServerOP_SetWorldTime = 0x200B
+ ServerOP_GetWorldTime = 0x200C
+ ServerOP_SyncWorldTime = 0x200E
+ ServerOP_CharTimeStamp = 0x200F
+ ServerOP_NameFilterCheck = 0x2011
+ ServerOP_BasicCharUpdate = 0x2012
+ ServerOP_CharacterCreate = 0x2013
+ ServerOP_NameCharUpdate = 0x2014
+ ServerOP_GetLatestTables = 0x2015
+ ServerOP_GetTableQuery = 0x2016
+ ServerOP_GetTableData = 0x2017
+ ServerOP_RaceUpdate = 0x2018
+ ServerOP_ZoneUpdate = 0x2019
+ ServerOP_BugReport = 0x201A
+ ServerOP_ResetDatabase = 0x201B
+ ServerOP_ZoneUpdates = 0x201C
+ ServerOP_LoginEquipment = 0x201D
+ ServerOP_CharacterPicture = 0x201E
+
+ // Zone management
+ ServerOP_LSZoneInfo = 0x3001
+ ServerOP_LSZoneStart = 0x3002
+ ServerOP_LSZoneBoot = 0x3003
+ ServerOP_LSZoneShutdown = 0x3004
+ ServerOP_LSZoneSleep = 0x3005
+ ServerOP_LSPlayerLeftWorld = 0x3006
+ ServerOP_LSPlayerJoinWorld = 0x3007
+ ServerOP_LSPlayerZoneChange = 0x3008
+
+ // Update operations
+ UpdateServerOP_Verified = 0x5090
+ UpdateServerOP_DisplayMsg = 0x5091
+ UpdateServerOP_Completed = 0x5092
+
+ // Special operations
+ ServerOP_UsertoWorldReq = 0xAB00
+ ServerOP_UsertoWorldResp = 0xAB01
+ ServerOP_WhoAll = 0x0210
+)
diff --git a/internal/opcodes/utils.go b/internal/opcodes/utils.go
new file mode 100644
index 0000000..1f45b68
--- /dev/null
+++ b/internal/opcodes/utils.go
@@ -0,0 +1,67 @@
+package opcodes
+
+// Constants for opcode management
+const (
+ MAX_EQ_OPCODE = 0xFFFF
+ APP_OPCODE_SIZE_1 = 1
+ APP_OPCODE_SIZE_2 = 2
+)
+
+// Opcode type definitions
+type (
+ ProtocolOpcode uint8
+ ServerOpcode uint16
+ AppOpcode uint16
+ EmuOpcode uint16
+)
+
+// Quick lookup maps for efficient opcode translation
+var (
+ ProtocolOpcodeNames = map[ProtocolOpcode]string{
+ OP_SessionRequest: "OP_SessionRequest",
+ OP_SessionResponse: "OP_SessionResponse",
+ OP_Combined: "OP_Combined",
+ OP_SessionDisconnect: "OP_SessionDisconnect",
+ OP_KeepAlive: "OP_KeepAlive",
+ OP_ServerKeyRequest: "OP_ServerKeyRequest",
+ OP_SessionStatResponse: "OP_SessionStatResponse",
+ OP_Packet: "OP_Packet",
+ OP_Fragment: "OP_Fragment",
+ OP_OutOfOrderAck: "OP_OutOfOrderAck",
+ OP_Ack: "OP_Ack",
+ OP_AppCombined: "OP_AppCombined",
+ OP_OutOfSession: "OP_OutOfSession",
+ }
+
+ ServerOpcodeNames = map[ServerOpcode]string{
+ ServerOP_KeepAlive: "ServerOP_KeepAlive",
+ ServerOP_ChannelMessage: "ServerOP_ChannelMessage",
+ ServerOP_SetZone: "ServerOP_SetZone",
+ ServerOP_ShutdownAll: "ServerOP_ShutdownAll",
+ // Add more as needed for performance-critical lookups
+ }
+)
+
+// Helper functions for efficient opcode operations
+func IsProtocolOpcode(op uint8) bool {
+ _, exists := ProtocolOpcodeNames[ProtocolOpcode(op)]
+ return exists
+}
+
+func IsServerOpcode(op uint16) bool {
+ return op >= 0x0001 && op <= 0xFFFF
+}
+
+func GetProtocolOpcodeName(op ProtocolOpcode) string {
+ if name, exists := ProtocolOpcodeNames[op]; exists {
+ return name
+ }
+ return "OP_Unknown"
+}
+
+func GetServerOpcodeName(op ServerOpcode) string {
+ if name, exists := ServerOpcodeNames[op]; exists {
+ return name
+ }
+ return "ServerOP_Unknown"
+}
diff --git a/source/LoginServer/EQ2 Login.sln b/source/LoginServer/EQ2 Login.sln
deleted file mode 100644
index 48169c7..0000000
--- a/source/LoginServer/EQ2 Login.sln
+++ /dev/null
@@ -1,25 +0,0 @@
-Microsoft Visual Studio Solution File, Format Version 11.00
-# Visual Studio 2010
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EQ2 Login", "Login.vcxproj", "{BE2C1914-FCCC-4F65-A7DD-105142B36104}"
-EndProject
-Global
- GlobalSection(SolutionConfigurationPlatforms) = preSolution
- Debug|Win32 = Debug|Win32
- EQ2Login|Win32 = EQ2Login|Win32
- MiniLogin Release|Win32 = MiniLogin Release|Win32
- Release|Win32 = Release|Win32
- EndGlobalSection
- GlobalSection(ProjectConfigurationPlatforms) = postSolution
- {BE2C1914-FCCC-4F65-A7DD-105142B36104}.Debug|Win32.ActiveCfg = EQ2Login|Win32
- {BE2C1914-FCCC-4F65-A7DD-105142B36104}.Debug|Win32.Build.0 = EQ2Login|Win32
- {BE2C1914-FCCC-4F65-A7DD-105142B36104}.EQ2Login|Win32.ActiveCfg = EQ2Login|Win32
- {BE2C1914-FCCC-4F65-A7DD-105142B36104}.EQ2Login|Win32.Build.0 = EQ2Login|Win32
- {BE2C1914-FCCC-4F65-A7DD-105142B36104}.MiniLogin Release|Win32.ActiveCfg = EQ2Login|Win32
- {BE2C1914-FCCC-4F65-A7DD-105142B36104}.MiniLogin Release|Win32.Build.0 = EQ2Login|Win32
- {BE2C1914-FCCC-4F65-A7DD-105142B36104}.Release|Win32.ActiveCfg = EQ2Login|Win32
- {BE2C1914-FCCC-4F65-A7DD-105142B36104}.Release|Win32.Build.0 = EQ2Login|Win32
- EndGlobalSection
- GlobalSection(SolutionProperties) = preSolution
- HideSolutionNode = FALSE
- EndGlobalSection
-EndGlobal
diff --git a/source/LoginServer/EQ2 Login.suo b/source/LoginServer/EQ2 Login.suo
deleted file mode 100644
index d090a9e..0000000
Binary files a/source/LoginServer/EQ2 Login.suo and /dev/null differ
diff --git a/source/LoginServer/Login.dsp b/source/LoginServer/Login.dsp
deleted file mode 100644
index cf66e51..0000000
--- a/source/LoginServer/Login.dsp
+++ /dev/null
@@ -1,447 +0,0 @@
-# Microsoft Developer Studio Project File - Name="Login" - Package Owner=<4>
-# Microsoft Developer Studio Generated Build File, Format Version 6.00
-# ** DO NOT EDIT **
-
-# TARGTYPE "Win32 (x86) Console Application" 0x0103
-
-CFG=Login - Win32 Debug
-!MESSAGE This is not a valid makefile. To build this project using NMAKE,
-!MESSAGE use the Export Makefile command and run
-!MESSAGE
-!MESSAGE NMAKE /f "Login.mak".
-!MESSAGE
-!MESSAGE You can specify a configuration when running NMAKE
-!MESSAGE by defining the macro CFG on the command line. For example:
-!MESSAGE
-!MESSAGE NMAKE /f "Login.mak" CFG="Login - Win32 Debug"
-!MESSAGE
-!MESSAGE Possible choices for configuration are:
-!MESSAGE
-!MESSAGE "Login - Win32 Release" (based on "Win32 (x86) Console Application")
-!MESSAGE "Login - Win32 Debug" (based on "Win32 (x86) Console Application")
-!MESSAGE "Login - Win32 MiniLogin" (based on "Win32 (x86) Console Application")
-!MESSAGE "Login - Win32 PublicLogin" (based on "Win32 (x86) Console Application")
-!MESSAGE
-
-# Begin Project
-# PROP AllowPerConfigDependencies 0
-# PROP Scc_ProjName ""
-# PROP Scc_LocalPath ""
-CPP=cl.exe
-RSC=rc.exe
-
-!IF "$(CFG)" == "Login - Win32 Release"
-
-# PROP BASE Use_MFC 0
-# PROP BASE Use_Debug_Libraries 0
-# PROP BASE Output_Dir "Release"
-# PROP BASE Intermediate_Dir "Release"
-# PROP BASE Target_Dir ""
-# PROP Use_MFC 0
-# PROP Use_Debug_Libraries 0
-# PROP Output_Dir "../Build"
-# PROP Intermediate_Dir "../Build/Login"
-# PROP Ignore_Export_Lib 0
-# PROP Target_Dir ""
-# ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c
-# ADD CPP /nologo /MT /w /W0 /GX /Zi /O2 /Ob2 /D "LOGINCRYPTO" /D "INVERSEXY" /D _WIN32_WINNT=0x0400 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /c
-# ADD BASE RSC /l 0x409 /d "NDEBUG"
-# ADD RSC /l 0x409 /d "NDEBUG"
-BSC32=bscmake.exe
-# ADD BASE BSC32 /nologo
-# ADD BSC32 /nologo /o"../Build/Login/Login.bsc"
-LINK32=link.exe
-# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /machine:I386
-# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /map:"../Build/Login.map" /debug /machine:I386
-# SUBTRACT LINK32 /pdb:none
-
-!ELSEIF "$(CFG)" == "Login - Win32 Debug"
-
-# PROP BASE Use_MFC 0
-# PROP BASE Use_Debug_Libraries 1
-# PROP BASE Output_Dir "Login___Win32_Debug"
-# PROP BASE Intermediate_Dir "Login___Win32_Debug"
-# PROP BASE Target_Dir ""
-# PROP Use_MFC 0
-# PROP Use_Debug_Libraries 1
-# PROP Output_Dir "../build/login/Debug"
-# PROP Intermediate_Dir "../build/login/debug"
-# PROP Ignore_Export_Lib 0
-# PROP Target_Dir ""
-# ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c
-# ADD CPP /nologo /MTd /Gm /GX /ZI /Od /D "LOGINCRYPTO" /D "INVERSEXY" /D _WIN32_WINNT=0x0400 /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /GZ /c
-# ADD BASE RSC /l 0x409 /d "_DEBUG"
-# ADD RSC /l 0x409 /d "_DEBUG"
-BSC32=bscmake.exe
-# ADD BASE BSC32 /nologo
-# ADD BSC32 /nologo
-LINK32=link.exe
-# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib /nologo /subsystem:console /debug /machine:I386 /pdbtype:sept
-# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /debug /machine:I386 /nodefaultlib:"LIBCMT" /out:"../build/login/Debug/LoginDebug.exe" /pdbtype:sept
-# SUBTRACT LINK32 /pdb:none
-
-!ELSEIF "$(CFG)" == "Login - Win32 MiniLogin"
-
-# PROP BASE Use_MFC 0
-# PROP BASE Use_Debug_Libraries 0
-# PROP BASE Output_Dir "Login___Win32_MiniLogin"
-# PROP BASE Intermediate_Dir "Login___Win32_MiniLogin"
-# PROP BASE Ignore_Export_Lib 0
-# PROP BASE Target_Dir ""
-# PROP Use_MFC 0
-# PROP Use_Debug_Libraries 0
-# PROP Output_Dir "../Build"
-# PROP Intermediate_Dir "../Build/MiniLogin"
-# PROP Ignore_Export_Lib 0
-# PROP Target_Dir ""
-# ADD BASE CPP /nologo /MT /w /W0 /GX /O2 /Ob2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "BUILD_FOR_WINDOWS" /FR /YX /FD /c
-# ADD CPP /nologo /MT /w /W0 /GX /O2 /Ob2 /D _WIN32_WINNT=0x0400 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "MINILOGIN" /FR /YX /FD /c
-# ADD BASE RSC /l 0x409 /d "NDEBUG"
-# ADD RSC /l 0x409 /d "NDEBUG"
-BSC32=bscmake.exe
-# ADD BASE BSC32 /nologo /o"../Build/Login/Login.bsc"
-# ADD BSC32 /nologo /o"../Build/MiniLogin/Login.bsc"
-LINK32=link.exe
-# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /machine:I386
-# SUBTRACT BASE LINK32 /pdb:none
-# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /machine:I386 /out:"../Build/MiniLogin.exe"
-# SUBTRACT LINK32 /pdb:none
-
-!ELSEIF "$(CFG)" == "Login - Win32 PublicLogin"
-
-# PROP BASE Use_MFC 0
-# PROP BASE Use_Debug_Libraries 0
-# PROP BASE Output_Dir "Login___Win32_PublicLogin"
-# PROP BASE Intermediate_Dir "Login___Win32_PublicLogin"
-# PROP BASE Ignore_Export_Lib 0
-# PROP BASE Target_Dir ""
-# PROP Use_MFC 0
-# PROP Use_Debug_Libraries 0
-# PROP Output_Dir "../Build"
-# PROP Intermediate_Dir "../Build/PublicLogin"
-# PROP Ignore_Export_Lib 0
-# PROP Target_Dir ""
-# ADD BASE CPP /nologo /MT /w /W0 /GX /O2 /Ob2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "BUILD_FOR_WINDOWS" /FR /YX /FD /c
-# ADD CPP /nologo /MT /w /W0 /GX /O2 /Ob2 /D _WIN32_WINNT=0x0400 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /D "PUBLICLOGIN" /FR /YX /FD /c
-# ADD BASE RSC /l 0x409 /d "NDEBUG"
-# ADD RSC /l 0x409 /d "NDEBUG"
-BSC32=bscmake.exe
-# ADD BASE BSC32 /nologo /o"../Build/Login/Login.bsc"
-# ADD BSC32 /nologo /o"../Build/Login/Login.bsc"
-LINK32=link.exe
-# ADD BASE LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /machine:I386
-# SUBTRACT BASE LINK32 /pdb:none
-# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib wsock32.lib zlib.lib mysqlclient.lib /nologo /subsystem:console /machine:I386 /out:"../Build/PublicLogin.exe"
-# SUBTRACT LINK32 /pdb:none
-
-!ENDIF
-
-# Begin Target
-
-# Name "Login - Win32 Release"
-# Name "Login - Win32 Debug"
-# Name "Login - Win32 MiniLogin"
-# Name "Login - Win32 PublicLogin"
-# Begin Group "Source Files"
-
-# PROP Default_Filter "cpp;c;cxx;rc;def;r;odl;idl;hpj;bat"
-# Begin Source File
-
-SOURCE=.\client.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=.\EQCrypto.cpp
-
-!IF "$(CFG)" == "Login - Win32 Release"
-
-!ELSEIF "$(CFG)" == "Login - Win32 Debug"
-
-!ELSEIF "$(CFG)" == "Login - Win32 MiniLogin"
-
-# PROP Exclude_From_Build 1
-
-!ELSEIF "$(CFG)" == "Login - Win32 PublicLogin"
-
-# PROP Exclude_From_Build 1
-
-!ENDIF
-
-# End Source File
-# Begin Source File
-
-SOURCE=.\logindatabase.cpp
-
-!IF "$(CFG)" == "Login - Win32 Release"
-
-!ELSEIF "$(CFG)" == "Login - Win32 Debug"
-
-!ELSEIF "$(CFG)" == "Login - Win32 MiniLogin"
-
-# PROP Exclude_From_Build 1
-
-!ELSEIF "$(CFG)" == "Login - Win32 PublicLogin"
-
-!ENDIF
-
-# End Source File
-# Begin Source File
-
-SOURCE=.\LWorld.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=.\net.cpp
-# End Source File
-# End Group
-# Begin Group "Header Files"
-
-# PROP Default_Filter "h;hpp;hxx;hm;inl"
-# Begin Source File
-
-SOURCE=.\client.h
-# End Source File
-# Begin Source File
-
-SOURCE=.\EQCrypto.h
-# End Source File
-# Begin Source File
-
-SOURCE=.\login_opcodes.h
-# End Source File
-# Begin Source File
-
-SOURCE=.\login_structs.h
-# End Source File
-# Begin Source File
-
-SOURCE=.\LWorld.h
-# End Source File
-# Begin Source File
-
-SOURCE=.\net.h
-# End Source File
-# End Group
-# Begin Group "Common Source Files"
-
-# PROP Default_Filter ".cpp"
-# Begin Source File
-
-SOURCE=..\common\crc32.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\database.cpp
-
-!IF "$(CFG)" == "Login - Win32 Release"
-
-!ELSEIF "$(CFG)" == "Login - Win32 Debug"
-
-!ELSEIF "$(CFG)" == "Login - Win32 MiniLogin"
-
-# PROP Exclude_From_Build 1
-
-!ELSEIF "$(CFG)" == "Login - Win32 PublicLogin"
-
-!ENDIF
-
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\dbcore.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\DBMemLeak.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\debug.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\EQNetwork.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\md5.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\MiscFunctions.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\Mutex.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\packet_dump.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\packet_functions.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\TCPConnection.cpp
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\timer.cpp
-# End Source File
-# End Group
-# Begin Group "Common Header Files"
-
-# PROP Default_Filter ".h"
-# Begin Source File
-
-SOURCE=..\common\classes.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\crc32.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\database.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\DBMemLeak.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\debug.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\deity.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\eq_opcodes.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\eq_packet_structs.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\EQCheckTable.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\EQFragment.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\EQNetwork.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\EQOpcodes.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\EQPacket.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\EQPacketManager.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\errmsg.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\Guilds.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\linked_list.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\md5.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\MiscFunctions.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\moremath.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\Mutex.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\packet_dump.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\packet_dump_file.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\packet_functions.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\queue.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\queues.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\races.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\Seperator.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\servertalk.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\TCPConnection.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\timer.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\types.h
-# End Source File
-# Begin Source File
-
-SOURCE=..\common\version.h
-# End Source File
-# End Group
-# Begin Group "Text Files"
-
-# PROP Default_Filter ""
-# Begin Source File
-
-SOURCE=.\Protocol.txt
-# End Source File
-# Begin Source File
-
-SOURCE=.\Tables.txt
-# End Source File
-# Begin Source File
-
-SOURCE=.\ThanksTo.txt
-# End Source File
-# End Group
-# End Target
-# End Project
diff --git a/source/LoginServer/Login.dsw b/source/LoginServer/Login.dsw
deleted file mode 100644
index 4ed0adf..0000000
--- a/source/LoginServer/Login.dsw
+++ /dev/null
@@ -1,29 +0,0 @@
-Microsoft Developer Studio Workspace File, Format Version 6.00
-# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE!
-
-###############################################################################
-
-Project: "Login"=.\Login.dsp - Package Owner=<4>
-
-Package=<5>
-{{{
-}}}
-
-Package=<4>
-{{{
-}}}
-
-###############################################################################
-
-Global:
-
-Package=<5>
-{{{
-}}}
-
-Package=<3>
-{{{
-}}}
-
-###############################################################################
-
diff --git a/source/LoginServer/Login.vcproj b/source/LoginServer/Login.vcproj
deleted file mode 100644
index 7c1f7a6..0000000
--- a/source/LoginServer/Login.vcproj
+++ /dev/null
@@ -1,542 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/source/LoginServer/Login.vcxproj b/source/LoginServer/Login.vcxproj
deleted file mode 100644
index 5622e75..0000000
--- a/source/LoginServer/Login.vcxproj
+++ /dev/null
@@ -1,154 +0,0 @@
-
-
-
-
- EQ2Login
- x64
-
-
-
- EQ2Login
- {BE2C1914-FCCC-4F65-A7DD-105142B36104}
- EQ2 Login
- 10.0
-
-
-
- v142
-
-
-
-
-
-
- <_ProjectFileVersion>10.0.30319.1
-
-
- $(SolutionDir)..\source\depends\mariadb-10.1.19\include;$(SolutionDir)..\source\depends\zlib\include;$(SolutionDir)..\source\depends\recastnavigation\Detour\Include;$(SolutionDir)..\source\depends\boost_1_72_0\;$(SolutionDir)..\source\depends\glm\;$(VC_IncludePath);$(WindowsSDK_IncludePath);
- $(SolutionDir)..\source\depends\recastnavigation\RecastDemo\Build\vs2019\lib\Debug;$(SolutionDir)..\source\depends\mariadb-10.1.19\lib\64-debug;$(SolutionDir)..\source\depends\zlib\lib;$(SolutionDir)..\source\depends\boost_1_72_0\lib64-msvc-14.2;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64);$(NETFXKitsDir)Lib\um\x64
- false
- $(SolutionDir)loginserver\
- .\$(ProjectName)__Debug64\
- $(ProjectName)__Debug64
-
-
-
- Disabled
- AnySuitable
- _WIN32_WINNT=0x0400;WIN32;NDEBUG;_CONSOLE;LOGIN; EQ2; EQN_DEBUG;_CRT_SECURE_NO_DEPRECATE;_HAS_STD_BYTE=0
-;%(PreprocessorDefinitions)
- EnableFastChecks
- MultiThreadedDebug
- false
- false
-
-
- $(IntDir)
-
-
- 4996;%(DisableSpecificWarnings)
- stdcpp17
-
-
- odbc32.lib;odbccp32.lib;ws2_32.lib;zlib.lib;mysqlclient.lib;DebugUtils.lib;Detour.lib;DetourCrowd.lib;DetourTileCache.lib;Recast.lib;%(AdditionalDependencies)
- LIBCMT;LIBC;%(IgnoreSpecificDefaultLibraries)
- true
- $(IntDir)$(TargetName).pdb
- true
- true
- Default
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/source/LoginServer/Login.vcxproj.filters b/source/LoginServer/Login.vcxproj.filters
deleted file mode 100644
index fae3865..0000000
--- a/source/LoginServer/Login.vcxproj.filters
+++ /dev/null
@@ -1,277 +0,0 @@
-
-
-
-
- {bfe8d6b0-594f-4b55-9f95-101bbcf4069c}
- cpp;c;cxx;rc;def;r;odl;idl;hpj;bat
-
-
- {d65b2760-468c-4206-a19a-48323a50ba5a}
- h;hpp;hxx;hm;inl
-
-
- {27b769a5-0972-4e9e-b78c-09ad3341579c}
- .cpp
-
-
- {11757e5a-691c-49c9-a627-df027ad58326}
- .h
-
-
- {99e7f9f9-abcd-4abf-8200-a4b5a467788c}
-
-
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
- Common Source Files
-
-
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- World Files
-
-
- World Files
-
-
- World Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
- Common Header Files
-
-
-
\ No newline at end of file
diff --git a/source/LoginServer/Login.vcxproj.user b/source/LoginServer/Login.vcxproj.user
deleted file mode 100644
index ace9a86..0000000
--- a/source/LoginServer/Login.vcxproj.user
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/source/LoginServer/Web/LoginWeb.o b/source/LoginServer/Web/LoginWeb.o
deleted file mode 100644
index 29c26ff..0000000
Binary files a/source/LoginServer/Web/LoginWeb.o and /dev/null differ
diff --git a/source/LoginServer/makefile b/source/LoginServer/makefile
deleted file mode 100644
index 5071072..0000000
--- a/source/LoginServer/makefile
+++ /dev/null
@@ -1,32 +0,0 @@
-APP=login
-SF= ../common/Log.o ../common/timer.o ../common/packet_dump.o ../common/unix.o \
- ../common/Mutex.o ../common/MiscFunctions.o LoginDatabase.o LoginAccount.o \
- ../common/TCPConnection.o ../common/emu_opcodes.o \
- client.o net.o PacketHeaders.o LWorld.o ../common/md5.o ../common/dbcore.o \
- Web/LoginWeb.o \
- ../common/EQEMuError.o ../common/misc.o ../common/Crypto.o ../common/RC4.o \
- .obj/debug.o .obj/database.o .obj/EQStream.o ../common/xmlParser.o \
- .obj/EQStreamFactory.o .obj/EQPacket.o ../common/CRC16.o ../common/packet_functions.o \
- ../common/Condition.o ../common/opcodemgr.o ../common/PacketStruct.o ../common/ConfigReader.o \
- ../common/DatabaseNew.o ../common/DatabaseResult.o ../common/Web/WebServer.o ../common/JsonParser.o
-
-CC=g++
-LINKER=gcc
-DFLAGS=-DEQ2 -DLOGIN
-WFLAGS=-Wall -Wuninitialized -Wwrite-strings -Wcast-qual -Wcomment -Wcast-align -Wno-deprecated
-COPTS=$(WFLAGS) -ggdb -march=native -pthread -pipe -DFX -D_GNU_SOURCE -DINVERSEXY $(DFLAGS) -I/usr/include/mariadb -I/usr/local/include/boost -I/usr/include/lua5.4 -std=c++17
-LINKOPTS=-rdynamic -L. -lstdc++ -lm -lz -L/usr/lib/x86_64-linux-gnu -lmariadb -lboost_system -lboost_thread -lboost_filesystem -lssl -lcrypto -lpthread -ldl
-all: $(APP)
-
-$(APP): $(SF)
- $(LINKER) $(COPTS) $(OBJS) $^ $(LINKOPTS) -o $@
-
-clean:
- rm -f $(SF) $(APP)
-
-%.o: %.cpp
- $(CC) -c $(COPTS) $< -o $@
-
-.obj/%.o: ../common/%.cpp ../common/%.h
- mkdir -p .obj
- $(CC) $(COPTS) -c $< -o $@
diff --git a/source/common/CRC16.cpp b/source/common/CRC16.cpp
deleted file mode 100644
index f7b43ad..0000000
--- a/source/common/CRC16.cpp
+++ /dev/null
@@ -1,328 +0,0 @@
-/*
- EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
-
- This file is part of EQ2Emulator.
-
- EQ2Emulator is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- EQ2Emulator is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with EQ2Emulator. If not, see .
-*/
-#include
-
-unsigned long IntArray[]={
-0x00000000,
-0x77073096,
-0xEE0E612C,
-0x990951BA,
-0x076DC419,
-0x706AF48F,
-0xE963A535,
-0x9E6495A3,
-0x0EDB8832,
-0x79DCB8A4,
-0xE0D5E91E,
-0x97D2D988,
-0x09B64C2B,
-0x7EB17CBD,
-0xE7B82D07,
-0x90BF1D91,
-0x1DB71064,
-0x6AB020F2,
-0xF3B97148,
-0x84BE41DE,
-0x1ADAD47D,
-0x6DDDE4EB,
-0xF4D4B551,
-0x83D385C7,
-0x136C9856,
-0x646BA8C0,
-0xFD62F97A,
-0x8A65C9EC,
-0x14015C4F,
-0x63066CD9,
-0xFA0F3D63,
-0x8D080DF5,
-0x3B6E20C8,
-0x4C69105E,
-0xD56041E4,
-0xA2677172,
-0x3C03E4D1,
-0x4B04D447,
-0xD20D85FD,
-0xA50AB56B,
-0x35B5A8FA,
-0x42B2986C,
-0xDBBBC9D6,
-0xACBCF940,
-0x32D86CE3,
-0x45DF5C75,
-0xDCD60DCF,
-0xABD13D59,
-0x26D930AC,
-0x51DE003A,
-0xC8D75180,
-0xBFD06116,
-0x21B4F4B5,
-0x56B3C423,
-0xCFBA9599,
-0xB8BDA50F,
-0x2802B89E,
-0x5F058808,
-0xC60CD9B2,
-0xB10BE924,
-0x2F6F7C87,
-0x58684C11,
-0xC1611DAB,
-0xB6662D3D,
-0x76DC4190,
-0x01DB7106,
-0x98D220BC,
-0xEFD5102A,
-0x71B18589,
-0x06B6B51F,
-0x9FBFE4A5,
-0xE8B8D433,
-0x7807C9A2,
-0x0F00F934,
-0x9609A88E,
-0xE10E9818,
-0x7F6A0DBB,
-0x086D3D2D,
-0x91646C97,
-0xE6635C01,
-0x6B6B51F4,
-0x1C6C6162,
-0x856530D8,
-0xF262004E,
-0x6C0695ED,
-0x1B01A57B,
-0x8208F4C1,
-0xF50FC457,
-0x65B0D9C6,
-0x12B7E950,
-0x8BBEB8EA,
-0xFCB9887C,
-0x62DD1DDF,
-0x15DA2D49,
-0x8CD37CF3,
-0xFBD44C65,
-0x4DB26158,
-0x3AB551CE,
-0xA3BC0074,
-0xD4BB30E2,
-0x4ADFA541,
-0x3DD895D7,
-0xA4D1C46D,
-0xD3D6F4FB,
-0x4369E96A,
-0x346ED9FC,
-0xAD678846,
-0xDA60B8D0,
-0x44042D73,
-0x33031DE5,
-0xAA0A4C5F,
-0xDD0D7CC9,
-0x5005713C,
-0x270241AA,
-0xBE0B1010,
-0xC90C2086,
-0x5768B525,
-0x206F85B3,
-0xB966D409,
-0xCE61E49F,
-0x5EDEF90E,
-0x29D9C998,
-0xB0D09822,
-0xC7D7A8B4,
-0x59B33D17,
-0x2EB40D81,
-0xB7BD5C3B,
-0xC0BA6CAD,
-0xEDB88320,
-0x9ABFB3B6,
-0x03B6E20C,
-0x74B1D29A,
-0xEAD54739,
-0x9DD277AF,
-0x04DB2615,
-0x73DC1683,
-0xE3630B12,
-0x94643B84,
-0x0D6D6A3E,
-0x7A6A5AA8,
-0xE40ECF0B,
-0x9309FF9D,
-0x0A00AE27,
-0x7D079EB1,
-0xF00F9344,
-0x8708A3D2,
-0x1E01F268,
-0x6906C2FE,
-0xF762575D,
-0x806567CB,
-0x196C3671,
-0x6E6B06E7,
-0xFED41B76,
-0x89D32BE0,
-0x10DA7A5A,
-0x67DD4ACC,
-0xF9B9DF6F,
-0x8EBEEFF9,
-0x17B7BE43,
-0x60B08ED5,
-0xD6D6A3E8,
-0xA1D1937E,
-0x38D8C2C4,
-0x4FDFF252,
-0xD1BB67F1,
-0xA6BC5767,
-0x3FB506DD,
-0x48B2364B,
-0xD80D2BDA,
-0xAF0A1B4C,
-0x36034AF6,
-0x41047A60,
-0xDF60EFC3,
-0xA867DF55,
-0x316E8EEF,
-0x4669BE79,
-0xCB61B38C,
-0xBC66831A,
-0x256FD2A0,
-0x5268E236,
-0xCC0C7795,
-0xBB0B4703,
-0x220216B9,
-0x5505262F,
-0xC5BA3BBE,
-0xB2BD0B28,
-0x2BB45A92,
-0x5CB36A04,
-0xC2D7FFA7,
-0xB5D0CF31,
-0x2CD99E8B,
-0x5BDEAE1D,
-0x9B64C2B0,
-0xEC63F226,
-0x756AA39C,
-0x026D930A,
-0x9C0906A9,
-0xEB0E363F,
-0x72076785,
-0x05005713,
-0x95BF4A82,
-0xE2B87A14,
-0x7BB12BAE,
-0x0CB61B38,
-0x92D28E9B,
-0xE5D5BE0D,
-0x7CDCEFB7,
-0x0BDBDF21,
-0x86D3D2D4,
-0xF1D4E242,
-0x68DDB3F8,
-0x1FDA836E,
-0x81BE16CD,
-0xF6B9265B,
-0x6FB077E1,
-0x18B74777,
-0x88085AE6,
-0xFF0F6A70,
-0x66063BCA,
-0x11010B5C,
-0x8F659EFF,
-0xF862AE69,
-0x616BFFD3,
-0x166CCF45,
-0xA00AE278,
-0xD70DD2EE,
-0x4E048354,
-0x3903B3C2,
-0xA7672661,
-0xD06016F7,
-0x4969474D,
-0x3E6E77DB,
-0xAED16A4A,
-0xD9D65ADC,
-0x40DF0B66,
-0x37D83BF0,
-0xA9BCAE53,
-0xDEBB9EC5,
-0x47B2CF7F,
-0x30B5FFE9,
-0xBDBDF21C,
-0xCABAC28A,
-0x53B39330,
-0x24B4A3A6,
-0xBAD03605,
-0xCDD70693,
-0x54DE5729,
-0x23D967BF,
-0xB3667A2E,
-0xC4614AB8,
-0x5D681B02,
-0x2A6F2B94,
-0xB40BBE37,
-0xC30C8EA1,
-0x5A05DF1B,
-0x2D02EF8D,
-};
-
-unsigned long CRC16(const unsigned char *buf, int size, int key)
-{
- unsigned long ecx = key; //mov ecx, [esp+arg_8]
- unsigned long eax = ecx; //mov eax, ecx
- unsigned long edi;
-
- eax = ~ eax; //not eax
- eax&=0xFF; //and eax, 0FFh
- eax=IntArray[eax]; //mov eax, dword_0_10115D38[eax*4] IntArray
- eax ^= 0x00FFFFFF; //xor eax, 0FFFFFFh
- int edx = ecx; //mov edx, ecx
- edx = edx >> 8; //sar edx, 8
- edx = edx ^ eax; //xor edx, eax
- eax = eax >> 8; //sar eax, 8
- edx &= 0xFF; //and edx, 0FFh
- eax &= 0x00FFFFFF; //and eax, 0FFFFFFh
- eax ^= IntArray[edx]; //xor eax, dword_0_10115D38[edx*4]
- edx = ecx; //mov edx, ecx
- edx = edx >> 0x10; //sar edx, 10h
- edx ^= eax; //xor edx, eax
- eax = eax >> 8; //sar eax, 8
- edx &= 0xFF; //and edx, 0FFh
- int esi = IntArray[edx]; //mov esi, dword_0_10115D38[edx*4]
- edx = size; //mov edx, [esp+4+arg_4]
- eax &= 0x00FFFFFF; //and eax, 0FFFFFFh
- eax ^= esi; //xor eax, esi
- ecx = ecx >> 0x18; //sar ecx, 18h
- ecx ^= eax; //xor ecx, eax
- ecx &= 0xFF; //and ecx, 0FFh
- esi = IntArray[ecx]; //mov esi, dword_0_10115D38[ecx*4]
- ecx = (int)*buf; //mov ecx, [esp+4+arg_0]
- eax = eax >> 8; //sar eax, 8
- eax &= 0x00FFFFFF; //and eax, 0FFFFFFh
- eax ^= esi; //xor eax, esi
- for(int x = 0; x < size; x++)
- { //eax is the crc, ecx is the current part of the buffer
- int edx = 0; //xor edx, edx
- edx = buf[x] & 0x00FF; //mov dl, [ecx]
-
- edx ^= eax; //xor edx, eax
- eax = eax >> 8; //sar eax, 8
- edx &= 0xFF; //and edx, 0FFh
- edi = IntArray[edx]; //mov edi, dword_0_10115D38[edx*4]
- eax &= 0x00FFFFFF; //and eax, 0FFFFFFh
- eax ^= edi; //xor eax, edi
- }
- return ~eax;
-}
diff --git a/source/common/CRC16.h b/source/common/CRC16.h
deleted file mode 100644
index 7aacd36..0000000
--- a/source/common/CRC16.h
+++ /dev/null
@@ -1,25 +0,0 @@
-/*
- EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
-
- This file is part of EQ2Emulator.
-
- EQ2Emulator is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- EQ2Emulator is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with EQ2Emulator. If not, see .
-*/
-#ifndef _CRC16_H
-#define _CRC16_H
-
-unsigned long CRC16(const unsigned char *buf, int size, int key);
-
-#endif
diff --git a/source/common/Common_Defines.h b/source/common/Common_Defines.h
deleted file mode 100644
index 4600d97..0000000
--- a/source/common/Common_Defines.h
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
-
- This file is part of EQ2Emulator.
-
- EQ2Emulator is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- EQ2Emulator is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with EQ2Emulator. If not, see .
-*/
-#define BASEDIR "./"
-
-#ifndef DB_INI_FILE
- #ifdef LOGIN
- #define DB_INI_FILE BASEDIR "login_db.ini"
- #else
- #define DB_INI_FILE BASEDIR "world_db.ini"
- #endif
-#endif
-
-#ifndef MAIN_CONFIG_FILE
- #define MAIN_CONFIG_FILE BASEDIR "server_config.json"
-#endif
\ No newline at end of file
diff --git a/source/common/Condition.cpp b/source/common/Condition.cpp
deleted file mode 100644
index 348c90a..0000000
--- a/source/common/Condition.cpp
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
-
- This file is part of EQ2Emulator.
-
- EQ2Emulator is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- EQ2Emulator is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with EQ2Emulator. If not, see .
-*/
-#include "debug.h"
-#include "Condition.h"
-
-#ifdef WIN32
-#else
-#include
-#include
-#include
-#endif
-
-#ifdef WIN32
-/*
-
- Windows does not support condition variables by default.
- So we use a simple hack of sleeping in wait() and doing
- nothing anywhere else.
-
- some possible places to look for ways to do this:
- http://www.cs.wustl.edu/~schmidt/win32-cv-1.html
-
- http://sources.redhat.com/pthreads-win32/
- http://sources.redhat.com/cgi-bin/cvsweb.cgi/pthreads/pthread_cond_signal.c?rev=1.7&content-type=text/x-cvsweb-markup&cvsroot=pthreads-win32
-
-*/
-
-#define CONDITION_HACK_GRANULARITY 4
-
-
-Condition::Condition()
-{
-}
-
-void Condition::Signal()
-{
-}
-
-void Condition::SignalAll()
-{
-}
-
-void Condition::Wait()
-{
- Sleep(CONDITION_HACK_GRANULARITY);
-}
-
-Condition::~Condition()
-{
-}
-
-
-#else //!WIN32
-
-Condition::Condition()
-{
- pthread_cond_init(&cond,NULL);
- pthread_mutex_init(&mutex,NULL);
-}
-
-void Condition::Signal()
-{
- pthread_mutex_lock(&mutex);
- pthread_cond_signal(&cond);
- pthread_mutex_unlock(&mutex);
-}
-
-void Condition::SignalAll()
-{
- pthread_mutex_lock(&mutex);
- pthread_cond_broadcast(&cond);
- pthread_mutex_unlock(&mutex);
-}
-
-void Condition::Wait()
-{
- pthread_mutex_lock(&mutex);
- pthread_cond_wait(&cond,&mutex);
- pthread_mutex_unlock(&mutex);
-}
-
-/*
-I commented this specifically because I think it might be very
-difficult to write a windows counterpart to it, so I would like
-to discourage its use until we can confirm that it can be reasonably
-implemented on windows.
-
-bool Condition::TimedWait(unsigned long usec)
-{
-struct timeval now;
-struct timespec timeout;
-int retcode=0;
- pthread_mutex_lock(&mutex);
- gettimeofday(&now,NULL);
- now.tv_usec+=usec;
- timeout.tv_sec = now.tv_sec + (now.tv_usec/1000000);
- timeout.tv_nsec = (now.tv_usec%1000000) *1000;
- //cout << "now=" << now.tv_sec << "."<.
-*/
-#ifndef __CONDITION_H
-#define __CONDITION_H
-
-#ifndef WIN32
-#include
-#endif
-
-//Sombody, someday needs to figure out how to implement a condition
-//system on windows...
-
-
-class Condition {
- private:
-#ifndef WIN32
- pthread_cond_t cond;
- pthread_mutex_t mutex;
-#endif
- public:
- Condition();
- void Signal();
- void SignalAll();
- void Wait();
-// bool TimedWait(unsigned long usec);
- ~Condition();
-};
-
-#endif
-
-
diff --git a/source/common/Crypto.cpp b/source/common/Crypto.cpp
deleted file mode 100644
index 369390e..0000000
--- a/source/common/Crypto.cpp
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
-
- This file is part of EQ2Emulator.
-
- EQ2Emulator is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- EQ2Emulator is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with EQ2Emulator. If not, see .
-*/
-#include "Crypto.h"
-#include
-#include "../common/packet_dump.h"
-
-using namespace std;
-void test();
-int64 Crypto::RSADecrypt(uchar* text, int16 size){
- int64 ret = 0;
- uchar* buffer = new uchar[8];
- for(int i=7;i>=0;i--)
- buffer[7-i] = text[i];
- memcpy(&ret, buffer, 8);
- safe_delete_array(buffer);
- return ret;
-}
-
-void Crypto::RC4Decrypt(uchar* text, int32 size){
- MCrypto.lock();
- client->Cypher(text, size);
- MCrypto.unlock();
-}
-
-void Crypto::RC4Encrypt(uchar* text, int32 size){
- MCrypto.lock();
- server->Cypher(text, size);
- MCrypto.unlock();
-}
-
diff --git a/source/common/Crypto.h b/source/common/Crypto.h
deleted file mode 100644
index d2c478b..0000000
--- a/source/common/Crypto.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
-
- This file is part of EQ2Emulator.
-
- EQ2Emulator is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- EQ2Emulator is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with EQ2Emulator. If not, see .
-*/
-#ifndef _CRYPTO_H
-#define _CRYPTO_H
-#include
-#include
-#include "RC4.h"
-#include "../common/types.h"
-
-using namespace std;
-class Crypto {
-public:
- ~Crypto(){ safe_delete(client); safe_delete(server); }
- Crypto() { rc4_key = 0; encrypted = false; client = 0; server = 0; };
-
- static int64 RSADecrypt(uchar* text, int16 size);
- void RC4Encrypt(uchar* text, int32 size);
- void RC4Decrypt(uchar* text, int32 size);
- int64 getRC4Key() { return rc4_key; }
- void setRC4Key(int64 key) {
- rc4_key = key;
- if(key > 0){
- encrypted = true;
- client = new RC4(~key);
- server = new RC4(key);
- uchar temp[20];
- client->Cypher(temp, 20);
- server->Cypher(temp, 20);
- }
- else{
- encrypted = false;
- safe_delete(client);
- safe_delete(server);
- }
- }
- bool isEncrypted(){ return encrypted; }
- void setEncrypted(bool in_val){ encrypted = in_val; }
-
-
-private:
- RC4* server;
- RC4* client;
- bool encrypted;
- int64 rc4_key;
- mutex MCrypto;
-};
-
-#endif
-
diff --git a/source/common/DataBuffer.h b/source/common/DataBuffer.h
deleted file mode 100644
index f40486d..0000000
--- a/source/common/DataBuffer.h
+++ /dev/null
@@ -1,207 +0,0 @@
-/*
- EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
-
- This file is part of EQ2Emulator.
-
- EQ2Emulator is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- EQ2Emulator is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with EQ2Emulator. If not, see .
-*/
-#ifndef __EQ2_DATABUFFER_
-#define __EQ2_DATABUFFER_
-#include
-#include "../common/types.h"
-#include "../common/EQPacket.h"
-#include "../common/EQ2_Common_Structs.h"
-
-#ifdef WORLD
- #include "../WorldServer/SpawnLists.h"
-#endif
-
-using namespace std;
-
-class DataBuffer{
-public:
- bool changed;
- uchar* getData(){ return (uchar*)buffer.c_str(); }
- int32 getDataSize(){ return buffer.length(); }
- string* getDataString(){ return &buffer; }
- void CreateEQ2Color(EQ2_Color& color){
- CreateEQ2Color(&color);
- }
- uchar* GetLoadBuffer(){
- return load_buffer;
- }
- int32 GetLoadPos(){
- return load_pos;
- }
- int32 GetLoadLen(){
- return load_len;
- }
- void SetLoadPos(int32 new_pos){
- load_pos = new_pos;
- }
- void CreateEQ2Color(EQ2_Color* color){
- int8 rgb[3];
- float* tmp = 0;
- for(int i=0;i<3;i++){
- tmp = (float*)(load_buffer + load_pos);
- rgb[i] = (int8)((*tmp)*255);
- load_pos += sizeof(float);
- }
- color->red = rgb[0];
- color->green = rgb[1];
- color->blue = rgb[2];
- }
-
- template void MakeEQ2_Int8(Type& output){
- MakeEQ2_Int8(&output);
- }
- template void MakeEQ2_Int8(Type* output){
- float* tmp = (float*)(load_buffer + load_pos);
- if(*tmp < 0)
- *tmp *= -1;
- sint8 result = (sint8)((*tmp)*100);
- memcpy(output, &result, sizeof(sint8));
- load_pos += sizeof(float);
- }
- void InitializeGetData(){
- get_buffer = (uchar*)buffer.c_str();
- get_len = buffer.length();
- get_pos = 0;
- }
- void InitializeLoadData(uchar* input, int32 size){
- buffer = string((char*)input, size);
- load_buffer = (uchar*)buffer.c_str();
- load_len = size;
- load_pos = 0;
- }
- template void LoadDataString(String& output){
- LoadDataString(&output);
- }
- template void LoadDataString(String* output){
- if((sizeof(output->size) + load_pos) <= load_len){
- memcpy(&output->size, load_buffer + load_pos, sizeof(output->size));
- load_pos += sizeof(output->size);
- }
- if((output->size + load_pos) <= load_len){
- output->data = string((char*)(load_buffer + load_pos), output->size);
- load_pos += output->size;
- }
- }
- template void LoadData(Type& output){
- LoadData(&output);
- }
- template void LoadData(Type* output, int32 array_size){
- if(array_size<=1){
- LoadData(output);
- }
- else{
- for(int32 i=0;i void LoadData(Type* output){
- if((sizeof(Type) + load_pos) <= load_len){
- memcpy(output, load_buffer + load_pos, sizeof(Type));
- load_pos += sizeof(Type);
- }
- }
- template void LoadData(Type& output, int32 array_size){
- LoadData(&output, array_size);
- }
- void LoadSkip(int8 bytes){
- load_pos += bytes;
- }
- template void LoadSkip(Type& skip){
- LoadSkip(&skip);
- }
- template void LoadSkip(Type* skip){
- load_pos += sizeof(Type);
- }
- template void GetData(Type* output){
- if((sizeof(Type) + get_pos) <= get_len){
- *output = (Type*)get_buffer;
- get_pos += sizeof(output);
- }
- }
- void AddZeros(int16 num){
- int8* data = new int8[num];
- memset(data, 0, num);
- AddData(*data);
- safe_delete_array(data);
- }
- template void StructAddData(Type input, int16 size, string* datastring){
- if(datastring)
- datastring->append((char*)&input, size);
- else
- buffer.append((char*)&input, size);
- }
- template void StructAddData(Type input, int32 array_size, int16 size, string* datastring){
- if(array_size>0){
- for(int32 i=0;i void AddData(Type input, string* datastring = 0){
- if(!datastring)
- datastring = &buffer;
- datastring->append((char*)&input, sizeof(input));
- }
- template void AddData(Type input, int32 array_size, string* datastring = 0){
- if(array_size>0){
- for(int32 i=0;i void AddDataString(String* input, string* datastring = 0){
- AddDataString(*input, datastring);
- }
- template void AddDataString(String input, string* datastring = 0){
- input.size = input.data.length();
- if(!datastring)
- datastring = &buffer;
- datastring->append((char*)&input.size, sizeof(input.size));
- datastring->append(input.data);
- }
- void AddCharArray(char* array, string* datastring = 0){
- if(!datastring)
- datastring = &buffer;
- datastring->append(array);
- }
- void AddCharArray(char* array, int16 size, string* datastring = 0){
- if(!datastring)
- datastring = &buffer;
- datastring->append(array, size);
- }
- void AddData(string data, string* datastring = 0){
- if(!datastring)
- datastring = &buffer;
- datastring->append(data);
- }
- void Clear() { buffer.clear(); }
-private:
- string buffer;
- uchar* get_buffer;
- uchar* load_buffer;
- int32 get_len;
- int32 get_pos;
- int32 load_len;
- int32 load_pos;
-};
-#endif
-
diff --git a/source/common/DatabaseNew.cpp b/source/common/DatabaseNew.cpp
deleted file mode 100644
index e1e2450..0000000
--- a/source/common/DatabaseNew.cpp
+++ /dev/null
@@ -1,422 +0,0 @@
-/*
- EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
-
- This file is part of EQ2Emulator.
-
- EQ2Emulator is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- EQ2Emulator is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with EQ2Emulator. If not, see .
-*/
-#include
-#include
-#include
-#include
-#include "Log.h"
-#include "DatabaseNew.h"
-#include
-
-//increase this if large queries are being run frequently to make less calls to malloc()
-#define QUERY_INITIAL_SIZE 512
-
-#if defined WORLD
-#define DB_INI "world_db.ini"
-#elif defined LOGIN
-#define DB_INI "login_db.ini"
-#elif defined PARSER
-#define DB_INI "parser_db.ini"
-#endif
-
-DatabaseNew::DatabaseNew() {
- mysql_init(&mysql);
- int timeout = 10;
- mysql_options(&mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout);
- MMysql.SetName("DatabaseNew::mysql");
-}
-
-DatabaseNew::~DatabaseNew() {
- mysql_close(&mysql);
-#if MYSQL_VERSION_ID >= 50003
- mysql_library_end();
-#else
- mysql_server_end();
-#endif
-}
-
-bool DatabaseNew::Connect() {
- char line[256], *key, *val;
- char host[256], user[64], password[64], database[64], port[64];
- bool found_section = false;
- FILE *f;
-
- if ((f = fopen(DB_INI, "r")) == NULL) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Unable to read %s\n", DB_INI);
- return false;
- }
-
- memset(host, 0, sizeof(host));
- memset(user, 0, sizeof(user));
- memset(password, 0, sizeof(password));
- memset(database, 0, sizeof(database));
- memset(port, 0, sizeof(port));
-
- while (fgets(line, sizeof(line), f) != NULL) {
- if (line[0] == '#' || line[0] == '\n' || line[0] == '\r')
- continue;
-
- if (!found_section) {
- if (strncasecmp(line, "[Database]", 10) == 0)
- found_section = true;
- }
- else {
- if ((key = strtok(line, "=")) != NULL) {
- if ((val = strtok(NULL, "\r\n")) != NULL) {
- if (strncasecmp(line, "host", 4) == 0)
- strncpy(host, val, sizeof(host) - 1);
- else if (strncasecmp(line, "user", 4) == 0)
- strncpy(user, val, sizeof(user) - 1);
- else if (strncasecmp(line, "password", 8) == 0)
- strncpy(password, val, sizeof(password) - 1);
- else if (strncasecmp(line, "database", 8) == 0)
- strncpy(database, val, sizeof(database) - 1);
- else if (strncasecmp(line, "port", 4) == 0)
- strncpy(port, val, sizeof(port) - 1);
- }
- }
- }
- }
-
- fclose(f);
-
- if (host[0] == '\0') {
- LogWrite(DATABASE__ERROR, 0, "Database", "Unknown 'host' in '%s'\n", DB_INI);
- return false;
- }
- if (user[0] == '\0') {
- LogWrite(DATABASE__ERROR, 0, "Database", "Unknown 'user' in '%s'\n", DB_INI);
- return false;
- }
- if (password[0] == '\0') {
- LogWrite(DATABASE__ERROR, 0, "Database", "Unknown 'password' in '%s'\n", DB_INI);
- return false;
- }
- if (database[0] == '\0') {
- LogWrite(DATABASE__ERROR, 0, "Database", "Unknown 'database' in '%s'\n", DB_INI);
- return false;
- }
-
- unsigned int portnum = atoul(port);
- return Connect(host, user, password, database, portnum);
-}
-
-bool DatabaseNew::Connect(const char *host, const char *user, const char *password, const char *database, unsigned int port) {
- if (mysql_real_connect(&mysql, host, user, password, database, port, NULL, 0) == NULL) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Unable to connect to MySQL server at %s:%u: %s\n", host, port, mysql_error(&mysql));
- return false;
- }
-
- return true;
-}
-
-bool DatabaseNew::Query(const char *query, ...) {
- char *buf;
- size_t size = QUERY_INITIAL_SIZE;
- int num_chars;
- va_list args;
- bool ret = true;
-
- MMysql.writelock(__FUNCTION__, __LINE__);
- while (true) {
- if ((buf = (char *)malloc(size)) == NULL) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate database query of %u bytes\n", size);
- MMysql.releasewritelock(__FUNCTION__, __LINE__);
- return false;
- }
-
- va_start(args, query);
- num_chars = vsnprintf(buf, size, query, args);
- va_end(args);
-
- if (num_chars > -1 && (size_t)num_chars < size)
- break;
-
- if (num_chars > -1)
- size = num_chars + 1;
- else
- size *= 2;
-
- free(buf);
- }
-
- if (mysql_real_query(&mysql, buf, num_chars) != 0) {
-
- if (mysql_errno(&mysql) == CR_SERVER_LOST || mysql_errno(&mysql) == CR_SERVER_GONE_ERROR) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Lost connection, attempting to recover and retry query...");
- Connect();
-
- // retry attempt of previous query (1 try and we give up)
- if (mysql_real_query(&mysql, buf, num_chars) != 0) {
- ret = false;
- }
- }
- else if (!IsIgnoredErrno(mysql_errno(&mysql))) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Error %i running MySQL query: %s\n%s\n", mysql_errno(&mysql), mysql_error(&mysql), buf);
- ret = false;
- }
- }
- free(buf);
-
- MMysql.releasewritelock(__FUNCTION__, __LINE__);
- return ret;
-}
-
-bool DatabaseNew::Select(DatabaseResult *result, const char *query, ...) {
- char *buf;
- size_t size = QUERY_INITIAL_SIZE;
- int num_chars;
- va_list args;
- MYSQL_RES *res;
- bool ret = true;
-
- MMysql.writelock(__FUNCTION__, __LINE__);
- while (true) {
- if ((buf = (char *)malloc(size)) == NULL) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate database query of %u bytes\n", size);
- MMysql.releasewritelock(__FUNCTION__, __LINE__);
- return false;
- }
-
- va_start(args, query);
- num_chars = vsnprintf(buf, size, query, args);
- va_end(args);
-
- if (num_chars > -1 && (size_t)num_chars < size)
- break;
-
- if (num_chars > -1)
- size = num_chars + 1;
- else
- size *= 2;
-
- free(buf);
- }
-
- if (mysql_real_query(&mysql, buf, (unsigned long)num_chars) != 0) {
-
- if (mysql_errno(&mysql) == CR_SERVER_LOST || mysql_errno(&mysql) == CR_SERVER_GONE_ERROR) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Lost connection, attempting to recover and retry query...");
-
- mysql_close(&mysql);
- Connect();
-
- // retry attempt of previous query (1 try and we give up)
- if (mysql_real_query(&mysql, buf, (unsigned long)num_chars) != 0) {
- ret = false;
- }
- }
- else if (!IsIgnoredErrno(mysql_errno(&mysql))) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Error %i running MySQL query: %s\n%s\n", mysql_errno(&mysql), mysql_error(&mysql), buf);
- ret = false;
- }
- }
-
- if (ret && !IsIgnoredErrno(mysql_errno(&mysql))) {
- res = mysql_store_result(&mysql);
-
- if (res != NULL)
- {
- // Grab number of rows and number of fields from the query
- uint8 num_rows = mysql_affected_rows(&mysql);
- uint8 num_fields = mysql_field_count(&mysql);
-
- ret = result->StoreResult(res, num_fields, num_rows);
- }
- else {
- LogWrite(DATABASE__ERROR, 0, "Database", "Error storing MySql query result (%d): %s\n%s", mysql_errno(&mysql), mysql_error(&mysql), buf);
- ret = false;
- }
- }
-
- free(buf);
-
- MMysql.releasewritelock(__FUNCTION__, __LINE__);
- return ret;
-}
-
-int32 DatabaseNew::LastInsertID()
-{
- return (int32)mysql_insert_id(&mysql);
-}
-
-long DatabaseNew::AffectedRows()
-{
- return mysql_affected_rows(&mysql);
-}
-
-char * DatabaseNew::Escape(const char *str, size_t len) {
- char *buf = (char *)malloc(len * 2 + 1);
-
- if (buf == NULL) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate %u bytes in %s:%u\n", len * 2 + 1, __FUNCTION__, __LINE__);
- return NULL;
- }
-
- mysql_real_escape_string(&mysql, buf, str, len);
- return buf;
-}
-
-char * DatabaseNew::Escape(const char *str) {
- return Escape(str, strlen(str));
-}
-
-string DatabaseNew::EscapeStr(const char *str, size_t len) {
- char *buf = (char *)malloc(len * 2 + 1);
- string ret;
-
- if (buf == NULL) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate %u bytes in %s:%u\n", len * 2 + 1, __FUNCTION__, __LINE__);
- return NULL;
- }
-
- mysql_real_escape_string(&mysql, buf, str, len);
- ret.append(buf);
- free(buf);
-
- return ret;
-}
-
-string DatabaseNew::EscapeStr(const char *str) {
- return EscapeStr(str, strlen(str));
-}
-
-string DatabaseNew::EscapeStr(string str) {
- return EscapeStr(str.c_str(), str.length());
-}
-
-bool DatabaseNew::QueriesFromFile(const char * file) {
- bool success = true;
- long size;
- char *buf;
- int ret;
- MYSQL_RES *res;
- FILE *f;
-
- f = fopen(file, "rb");
- if (f == NULL) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Unable to open '%s' for reading: %s", file, strerror(errno));
- return false;
- }
-
- fseek(f, 0, SEEK_END);
- size = ftell(f);
- fseek(f, 0, SEEK_SET);
-
- buf = (char *)malloc(size + 1);
- if (buf == NULL) {
- fclose(f);
- LogWrite(DATABASE__ERROR, 0, "Database", "Out of memory trying to allocate %u bytes in %s:%u\n", size + 1, __FUNCTION__, __LINE__);
- return false;
- }
-
- if (fread(buf, sizeof(*buf), size, f) != (size_t)size) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Failed to read from '%s': %s", file, strerror(errno));
- fclose(f);
- free(buf);
- return false;
- }
-
- buf[size] = '\0';
- fclose(f);
-
- mysql_set_server_option(&mysql, MYSQL_OPTION_MULTI_STATEMENTS_ON);
- ret = mysql_real_query(&mysql, buf, size);
- free(buf);
-
- if (ret != 0) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Error running MySQL queries from file '%s' (%d): %s", file, mysql_errno(&mysql), mysql_error(&mysql));
- success = false;
- }
- else {
- //all results must be processed
- do {
- res = mysql_store_result(&mysql);
- if (res != NULL)
- mysql_free_result(res);
- ret = mysql_next_result(&mysql);
-
- if (ret > 0) {
- LogWrite(DATABASE__ERROR, 0, "Database", "Error running MySQL queries from file '%s' (%d): %s", file, mysql_errno(&mysql), mysql_error(&mysql));
- success = false;
- }
-
- } while (ret == 0);
- }
- mysql_set_server_option(&mysql, MYSQL_OPTION_MULTI_STATEMENTS_OFF);
-
- return success;
-}
-
-void DatabaseNew::SetIgnoredErrno(unsigned int db_errno) {
- vector::iterator itr;
-
- for (itr = ignored_errnos.begin(); itr != ignored_errnos.end(); itr++) {
- if ((*itr) == db_errno)
- return;
- }
-
- ignored_errnos.push_back(db_errno);
-}
-
-void DatabaseNew::RemoveIgnoredErrno(unsigned int db_errno) {
- vector::iterator itr;
-
- for (itr = ignored_errnos.begin(); itr != ignored_errnos.end(); itr++) {
- if ((*itr) == db_errno) {
- ignored_errnos.erase(itr);
- break;
- }
- }
-}
-
-bool DatabaseNew::IsIgnoredErrno(unsigned int db_errno) {
- vector::iterator itr;
-
- for (itr = ignored_errnos.begin(); itr != ignored_errnos.end(); itr++) {
- if ((*itr) == db_errno)
- return true;
- }
-
- return false;
-}
-
-// Sends the MySQL server a keepalive
-void DatabaseNew::PingNewDB() {
- MMysql.writelock(__FUNCTION__, __LINE__);
- mysql_ping(&mysql);
-
- int32* errnum = new int32;
- *errnum = mysql_errno(&mysql);
-
- switch (*errnum)
- {
- case CR_COMMANDS_OUT_OF_SYNC:
- case CR_SERVER_GONE_ERROR:
- case CR_UNKNOWN_ERROR:
- {
- LogWrite(DATABASE__ERROR, 0, "Database", "[Database] We lost connection to the database., errno: %i", errno);
- break;
- }
- }
-
- safe_delete(errnum);
- MMysql.releasewritelock(__FUNCTION__, __LINE__);
-}
\ No newline at end of file
diff --git a/source/common/DatabaseNew.h b/source/common/DatabaseNew.h
deleted file mode 100644
index 2e2e002..0000000
--- a/source/common/DatabaseNew.h
+++ /dev/null
@@ -1,48 +0,0 @@
-#ifndef COMMON_DATABASE_H_
-#define COMMON_DATABASE_H_
-
-#include
-#include "DatabaseResult.h"
-
-using namespace std;
-
-class DatabaseNew {
-public:
- DatabaseNew();
- virtual ~DatabaseNew();
-
- unsigned int GetError() {return mysql_errno(&mysql);}
- const char * GetErrorMsg() {return mysql_error(&mysql);}
-
- bool Connect();
- bool Connect(const char *host, const char *user, const char *password, const char *database, unsigned int port = 3306);
-
- bool Query(const char *query, ...);
- bool Select(DatabaseResult *result, const char *query, ...);
-
- int32 LastInsertID();
- long AffectedRows();
-
- //these two must free() the return char* after it's used in a query
- char * Escape(const char *str, size_t len);
- char * Escape(const char *str);
-
- //does not need free()
- string EscapeStr(const char *str, size_t len);
- string EscapeStr(const char *str);
- string EscapeStr(string str);
-
- bool QueriesFromFile(const char *file);
- void SetIgnoredErrno(unsigned int db_errno);
- void RemoveIgnoredErrno(unsigned int db_errno);
- bool IsIgnoredErrno(unsigned int db_errno);
-
- void PingNewDB();
-private:
- MYSQL mysql;
- Mutex MMysql;
-
- vector ignored_errnos;
-};
-
-#endif
diff --git a/source/common/DatabaseResult.cpp b/source/common/DatabaseResult.cpp
deleted file mode 100644
index 05df1a8..0000000
--- a/source/common/DatabaseResult.cpp
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- EQ2Emulator: Everquest II Server Emulator
- Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net)
-
- This file is part of EQ2Emulator.
-
- EQ2Emulator is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- EQ2Emulator is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with EQ2Emulator. If not, see .
-*/
-#include
-#include
-#include "Log.h"
-#include "DatabaseResult.h"
-
-//enforced by MySQL...couldn't find a #define in their headers though
-#define FIELD_NAME_MAX 64
-
-//return this instead of NULL for certain functions to prevent crashes from coding errors
-static const char *empty_str = "";
-
-DatabaseResult::DatabaseResult(): field_map(), result(0), num_fields(0), row(0) {
-}
-
-DatabaseResult::~DatabaseResult() {
- unsigned int i;
-
- if (result != NULL)
- mysql_free_result(result);
-
- if (field_map.size()) {
- field_map.clear();
- }
-}
-
-bool DatabaseResult::StoreResult(MYSQL_RES* res, uint8 field_count, uint8 row_count) {
-
- //clear any previously stored result
- if (result != NULL)
- mysql_free_result(result);
-
- //clear any field names from a previous result
- if (field_map.size()) {
- field_map.clear();
- }
-
- result = res;
- num_rows = row_count;
- num_fields = field_count;
-
- // No rows or fields then we don't care
- if (!num_rows || !num_fields) {
- mysql_free_result(res);
- result = NULL;
- return false;
- }
-
-
- const MYSQL_FIELD* fields = mysql_fetch_fields(result);
-
- for (uint8 i = 0; i < num_fields; ++i) {
- field_map.emplace(std::make_pair(std::string_view(fields[i].name), i));
- }
-
- return true;
-}
-
-const char * DatabaseResult::GetFieldValue(unsigned int index) {
- if (index >= num_fields) {
- LogWrite(DATABASE__ERROR, 0, "Database Result", "Attempt to access field at index %u but there %s only %u field%s", index, num_fields == 1 ? "is" : "are", num_fields, num_fields == 1 ? "" : "s");
- return NULL;
- }
-
- return row[index];
-}
-
-const char * DatabaseResult::GetFieldValueStr(const char *field_name) {
- const auto& map_iterator = field_map.find(std::string_view(field_name));
- if (map_iterator != field_map.end()) {
- return row[map_iterator->second];
- }
-
- LogWrite(DATABASE__ERROR, 0, "Database Result", "Unknown field name '%s'", field_name);
- return NULL;
-}
-
-bool DatabaseResult::Next() {
- return (result != NULL && (row = mysql_fetch_row(result)) != NULL);
-}
-
-bool DatabaseResult::IsNull(unsigned int index) {
- const char *value = GetFieldValue(index);
- return value == NULL;
-}
-
-bool DatabaseResult::IsNullStr(const char *field_name) {
- const char *value = GetFieldValueStr(field_name);
- return value == NULL;
-}
-
-int8 DatabaseResult::GetInt8(unsigned int index) {
- const char *value = GetFieldValue(index);
- return value == NULL ? 0 : atoi(value);
-}
-
-int8 DatabaseResult::GetInt8Str(const char *field_name) {
- const char *value = GetFieldValueStr(field_name);
- return value == NULL ? 0 : atoi(value);
-}
-
-sint8 DatabaseResult::GetSInt8(unsigned int index) {
- const char *value = GetFieldValue(index);
- return value == NULL ? 0 : atoi(value);
-}
-
-sint8 DatabaseResult::GetSInt8Str(const char *field_name) {
- const char *value = GetFieldValueStr(field_name);
- return value == NULL ? 0 : atoi(value);
-}
-
-int16 DatabaseResult::GetInt16(unsigned int index) {
- const char *value = GetFieldValue(index);
- return value == NULL ? 0 : atoi(value);
-}
-
-int16 DatabaseResult::GetInt16Str(const char *field_name) {
- const char *value = GetFieldValueStr(field_name);
- return value == NULL ? 0 : atoi(value);
-}
-
-sint16 DatabaseResult::GetSInt16(unsigned int index) {
- const char *value = GetFieldValue(index);
- return value == NULL ? 0 : atoi(value);
-}
-
-sint16 DatabaseResult::GetSInt16Str(const char *field_name) {
- const char *value = GetFieldValueStr(field_name);
- return value == NULL ? 0 : atoi(value);
-}
-
-int32 DatabaseResult::GetInt32(unsigned int index) {
- const char *value = GetFieldValue(index);
- return value == NULL ? 0U : strtoul(value, NULL, 10);
-}
-
-int32 DatabaseResult::GetInt32Str(const char *field_name) {
- const char *value = GetFieldValueStr(field_name);
- return value == NULL ? 0U : strtoul(value, NULL, 10);
-}
-
-sint32 DatabaseResult::GetSInt32(unsigned int index) {
- const char *value = GetFieldValue(index);
- return value == NULL ? 0 : atoi(value);
-}
-
-sint32 DatabaseResult::GetSInt32Str(const char *field_name) {
- const char *value = GetFieldValueStr(field_name);
- return value == NULL ? 0 : atoi(value);
-}
-
-uint64 DatabaseResult::GetInt64(unsigned int index) {
- const char *value = GetFieldValue(index);
-#ifdef _WIN32
- return value == NULL ? 0UL : _strtoui64(value, NULL, 10);
-#else
- return value == NULL ? 0UL : strtoull(value, NULL, 10);
-#endif
-}
-
-uint64 DatabaseResult::GetInt64Str(const char *field_name) {
- const char *value = GetFieldValueStr(field_name);
-#ifdef _WIN32
- return value == NULL ? 0UL : _strtoui64(value, NULL, 10);
-#else
- return value == NULL ? 0UL : strtoull(value, NULL, 10);
-#endif
-}
-
-sint64 DatabaseResult::GetSInt64(unsigned int index) {
- const char *value = GetFieldValue(index);
-#ifdef _WIN32
- return value == NULL ? 0L : _strtoi64(value, NULL, 10);
-#else
- return value == NULL ? 0L : strtoll(value, NULL, 10);
-#endif
-}
-
-sint64 DatabaseResult::GetSInt64Str(const char *field_name) {
- const char *value = GetFieldValueStr(field_name);
-#ifdef _WIN32
- return value == NULL ? 0L : _strtoi64(value, NULL, 10);
-#else
- return value == NULL ? 0L : strtoll(value, NULL, 10);
-#endif
-}
-
-float DatabaseResult::GetFloat(unsigned int index) {
- const char *value = GetFieldValue(index);
- return value == NULL ? 0.0F : atof(value);
-}
-
-float DatabaseResult::GetFloatStr(const char *field_name) {
- const char *value = GetFieldValueStr(field_name);
- return value == NULL ? 0.0F : atof(value);
-}
-
-char DatabaseResult::GetChar(unsigned int index) {
- const char *value = GetFieldValue(index);
- return value == NULL ? '\0' : value[0];
-}
-
-char DatabaseResult::GetCharStr(const char *field_name) {
- const char *value = GetFieldValueStr(field_name);
- return value == NULL ? '\0' : value[0];
-}
-
-const char * DatabaseResult::GetString(unsigned int index) {
- const char *value = GetFieldValue(index);
- return value == NULL ? empty_str : value;
-}
-
-const char * DatabaseResult::GetStringStr(const char *field_name) {
- const char *value = GetFieldValueStr(field_name);
- return value == NULL ? empty_str : value;
-}
diff --git a/source/common/DatabaseResult.h b/source/common/DatabaseResult.h
deleted file mode 100644
index 36b3c3e..0000000
--- a/source/common/DatabaseResult.h
+++ /dev/null
@@ -1,57 +0,0 @@
-#ifndef COMMON_DATABASERESULT_H_
-#define COMMON_DATABASERESULT_H_
-
-#include "types.h"
-#ifdef _WIN32
-#include //#include when we/if we go to winsock2 :/
-#endif
-#include
-#include