253 lines
7.5 KiB
Go
253 lines
7.5 KiB
Go
package eq2net
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"time"
|
|
|
|
_ "github.com/go-sql-driver/mysql"
|
|
)
|
|
|
|
// OpcodeDBLoader handles loading opcodes from a MySQL database
|
|
// This keeps database concerns separate from the core opcode system
|
|
type OpcodeDBLoader struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// NewOpcodeDBLoader creates a new database loader
|
|
func NewOpcodeDBLoader(db *sql.DB) *OpcodeDBLoader {
|
|
return &OpcodeDBLoader{db: db}
|
|
}
|
|
|
|
// LoadVersions loads version ranges from the database
|
|
// Executes: SELECT DISTINCT version_range1, version_range2 FROM opcodes
|
|
func (l *OpcodeDBLoader) LoadVersions() (OpcodeVersionMap, error) {
|
|
query := `SELECT DISTINCT version_range1, version_range2 FROM opcodes ORDER BY version_range1`
|
|
|
|
rows, err := l.db.Query(query)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query version ranges: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
versions := make(OpcodeVersionMap)
|
|
for rows.Next() {
|
|
var minVersion, maxVersion uint16
|
|
if err := rows.Scan(&minVersion, &maxVersion); err != nil {
|
|
return nil, fmt.Errorf("failed to scan version range: %w", err)
|
|
}
|
|
versions[minVersion] = maxVersion
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating version rows: %w", err)
|
|
}
|
|
|
|
return versions, nil
|
|
}
|
|
|
|
// LoadOpcodes loads opcodes for a specific version
|
|
// Executes: SELECT name, opcode FROM opcodes WHERE ? BETWEEN version_range1 AND version_range2
|
|
func (l *OpcodeDBLoader) LoadOpcodes(version uint16) (map[string]uint16, error) {
|
|
query := `
|
|
SELECT name, opcode
|
|
FROM opcodes
|
|
WHERE ? BETWEEN version_range1 AND version_range2
|
|
ORDER BY version_range1, id
|
|
`
|
|
|
|
rows, err := l.db.Query(query, version)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to query opcodes for version %d: %w", version, err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
opcodes := make(map[string]uint16)
|
|
for rows.Next() {
|
|
var name string
|
|
var opcode uint16
|
|
if err := rows.Scan(&name, &opcode); err != nil {
|
|
return nil, fmt.Errorf("failed to scan opcode row: %w", err)
|
|
}
|
|
opcodes[name] = opcode
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("error iterating opcode rows: %w", err)
|
|
}
|
|
|
|
return opcodes, nil
|
|
}
|
|
|
|
// LoadAllOpcodes loads all opcodes for all versions at once
|
|
// More efficient for server initialization
|
|
func (l *OpcodeDBLoader) LoadAllOpcodes() (map[uint16]map[string]uint16, error) {
|
|
// First get all unique version ranges
|
|
versions, err := l.LoadVersions()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Load opcodes for each version
|
|
result := make(map[uint16]map[string]uint16)
|
|
for minVersion := range versions {
|
|
opcodes, err := l.LoadOpcodes(minVersion)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to load opcodes for version %d: %w", minVersion, err)
|
|
}
|
|
result[minVersion] = opcodes
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// InitializeOpcodeSystemFromDB initializes the opcode system from a database
|
|
func InitializeOpcodeSystemFromDB(db *sql.DB) (EQOpcodeManagerMap, OpcodeVersionMap, error) {
|
|
loader := NewOpcodeDBLoader(db)
|
|
|
|
// Load version ranges
|
|
versions, err := loader.LoadVersions()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to load version ranges: %w", err)
|
|
}
|
|
|
|
// Load all opcodes
|
|
opcodesByVersion, err := loader.LoadAllOpcodes()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("failed to load opcodes: %w", err)
|
|
}
|
|
|
|
// Create and initialize the opcode manager
|
|
opcodeManager := NewEQOpcodeManager()
|
|
if err := opcodeManager.LoadFromDatabase(versions, opcodesByVersion); err != nil {
|
|
return nil, nil, fmt.Errorf("failed to initialize opcode manager: %w", err)
|
|
}
|
|
|
|
log.Printf("Loaded opcodes for %d version ranges", len(versions))
|
|
for minVersion, maxVersion := range versions {
|
|
if opcodes, exists := opcodesByVersion[minVersion]; exists {
|
|
log.Printf(" Version %d-%d: %d opcodes", minVersion, maxVersion, len(opcodes))
|
|
}
|
|
}
|
|
|
|
return opcodeManager, versions, nil
|
|
}
|
|
|
|
// Example usage showing how to use the opcode system with a database
|
|
func ExampleDatabaseUsage() {
|
|
// Connect to database
|
|
dsn := "root:Root12!@tcp(localhost:3306)/eq2db?parseTime=true"
|
|
db, err := sql.Open("mysql", dsn)
|
|
if err != nil {
|
|
log.Fatalf("Failed to open database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Configure connection pool
|
|
db.SetMaxOpenConns(25)
|
|
db.SetMaxIdleConns(5)
|
|
db.SetConnMaxLifetime(5 * time.Minute)
|
|
|
|
// Test connection
|
|
if err := db.Ping(); err != nil {
|
|
log.Fatalf("Failed to ping database: %v", err)
|
|
}
|
|
|
|
// Initialize the opcode system
|
|
opcodeManager, versionMap, err := InitializeOpcodeSystemFromDB(db)
|
|
if err != nil {
|
|
log.Fatalf("Failed to initialize opcode system: %v", err)
|
|
}
|
|
|
|
// Example: Handle a client with version 1193
|
|
clientVersion := uint16(1193)
|
|
|
|
// Get the appropriate opcode manager for this client
|
|
manager := opcodeManager.GetManagerForClient(clientVersion, versionMap)
|
|
if manager == nil {
|
|
log.Fatalf("No opcode manager available for client version %d", clientVersion)
|
|
}
|
|
|
|
// Convert opcodes as needed
|
|
emuOpcode := OP_LoginRequestMsg
|
|
eqOpcode := manager.EmuToEQ(emuOpcode)
|
|
log.Printf("Client %d: %s -> 0x%04X", clientVersion, manager.EmuToName(emuOpcode), eqOpcode)
|
|
|
|
// Reverse conversion
|
|
emuOpcode = manager.EQToEmu(eqOpcode)
|
|
log.Printf("Client %d: 0x%04X -> %s", clientVersion, eqOpcode, manager.EmuToName(emuOpcode))
|
|
}
|
|
|
|
// CreateOpcodeTableSQL returns the SQL to create the opcodes table
|
|
// This matches the existing EQ2 schema
|
|
func CreateOpcodeTableSQL() string {
|
|
return `
|
|
CREATE TABLE IF NOT EXISTS opcodes (
|
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
|
version_range1 INT NOT NULL,
|
|
version_range2 INT NOT NULL,
|
|
name VARCHAR(64) NOT NULL,
|
|
opcode INT NOT NULL,
|
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
INDEX idx_version_range (version_range1, version_range2),
|
|
INDEX idx_name (name)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
|
`
|
|
}
|
|
|
|
// InsertExampleOpcodes inserts example opcodes for testing
|
|
func InsertExampleOpcodes(db *sql.DB) error {
|
|
// Example data for version 1185-1197 (includes client version 1193)
|
|
opcodes := []struct {
|
|
versionMin uint16
|
|
versionMax uint16
|
|
name string
|
|
opcode uint16
|
|
}{
|
|
{1185, 1197, "OP_LoginRequestMsg", 0x00B3},
|
|
{1185, 1197, "OP_LoginReplyMsg", 0x00B6},
|
|
{1185, 1197, "OP_WorldListMsg", 0x00B8},
|
|
{1185, 1197, "OP_PlayCharacterRequestMsg", 0x00BE},
|
|
{1185, 1197, "OP_PlayCharacterReplyMsg", 0x00BF},
|
|
{1185, 1197, "OP_DeleteCharacterRequestMsg", 0x00BA},
|
|
{1185, 1197, "OP_CreateCharacterRequestMsg", 0x00BC},
|
|
|
|
// Example data for version 1198-1207
|
|
{1198, 1207, "OP_LoginRequestMsg", 0x00C3},
|
|
{1198, 1207, "OP_LoginReplyMsg", 0x00C6},
|
|
{1198, 1207, "OP_WorldListMsg", 0x00C8},
|
|
{1198, 1207, "OP_PlayCharacterRequestMsg", 0x00CE},
|
|
{1198, 1207, "OP_PlayCharacterReplyMsg", 0x00CF},
|
|
|
|
// Example data for version 1208-1211
|
|
{1208, 1211, "OP_LoginRequestMsg", 0x00D3},
|
|
{1208, 1211, "OP_LoginReplyMsg", 0x00D6},
|
|
{1208, 1211, "OP_WorldListMsg", 0x00D8},
|
|
{1208, 1211, "OP_PlayCharacterRequestMsg", 0x00DE},
|
|
{1208, 1211, "OP_PlayCharacterReplyMsg", 0x00DF},
|
|
}
|
|
|
|
// Prepare insert statement
|
|
stmt, err := db.Prepare(`
|
|
INSERT INTO opcodes (version_range1, version_range2, name, opcode)
|
|
VALUES (?, ?, ?, ?)
|
|
ON DUPLICATE KEY UPDATE opcode = VALUES(opcode)
|
|
`)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to prepare statement: %w", err)
|
|
}
|
|
defer stmt.Close()
|
|
|
|
// Insert all opcodes
|
|
for _, op := range opcodes {
|
|
if _, err := stmt.Exec(op.versionMin, op.versionMax, op.name, op.opcode); err != nil {
|
|
return fmt.Errorf("failed to insert opcode %s: %w", op.name, err)
|
|
}
|
|
}
|
|
|
|
log.Printf("Inserted %d example opcodes", len(opcodes))
|
|
return nil
|
|
}
|