Common opcodes library with database support.
Find a file
2025-12-26 15:37:12 -06:00
tools Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
constants_login.go Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
constants_shared.go Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
constants_world.go Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
go.mod Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
go.sum Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
integration_test.go Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
loader.go Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
manager.go Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
manager_test.go Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
names.go Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
opcode.go Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
README.md Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00
sets.go Bring opcodes to parity with C++ 2025-12-26 15:37:12 -06:00

EQ2 Opcodes Package

Go package for managing EQ2 emulator opcodes with database loading and version support.

Overview

This package provides type-safe opcode management for EQ2 emulator servers, mirroring the C++ opcode system with improved testability through dependency injection. It supports multiple client versions and maintains bidirectional translation between internal opcodes and wire protocol values.

Features

  • Type-safe opcodes: Opcode type (int16) with string names
  • Database loading: Load opcodes from MySQL database using the same queries as C++
  • Version management: Support multiple client versions simultaneously
  • Bidirectional translation: Convert between internal opcodes and wire protocol values
  • Thread-safe: Concurrent access via RWMutex
  • Independently testable: Mock loader for unit tests without database
  • No build flags: Function-based API for login vs world server opcodes

Architecture

Core Types

  • Opcode (int16): Type-safe opcode identifier
  • Manager: Handles opcode translation for a specific client version
  • Registry: Manages multiple opcode managers for different versions
  • Loader: Interface for loading opcodes from database or mock sources

Opcode Organization

Opcodes are organized into three categories:

  1. Shared (21 opcodes): Used by both login and world servers
  2. Login (18 opcodes): Login server specific
  3. World (465 opcodes): World server specific

Total: 504 opcodes

Usage

Basic Example

package main

import (
    "context"
    "fmt"
    "git.sharkk.net/eq2go/db"
    "git.sharkk.net/eq2go/opcodes"
)

func main() {
    // Connect to database
    backend, err := db.New("mysql", db.Config{
        Host:     "localhost",
        Database: "eq2ls",
        Username: "eq2",
        Password: "eq2pass",
    })
    if err != nil {
        panic(err)
    }
    defer backend.Close()

    // Create loader and registry
    loader := opcodes.NewDatabaseLoader(backend)
    registry := opcodes.NewRegistry()

    // Load all available client versions
    if err := registry.LoadAllVersions(context.Background(), loader); err != nil {
        panic(err)
    }

    // Get manager for client version 1193
    mgr, ok := registry.GetManager(1193)
    if !ok {
        panic("Version 1193 not found")
    }

    // Translate internal opcode to wire protocol
    wireOp, ok := mgr.EmuToEQ(opcodes.OP_LoginRequestMsg)
    if !ok {
        panic("Opcode not found for this version")
    }
    fmt.Printf("OP_LoginRequestMsg -> 0x%04x\n", wireOp)

    // Translate wire protocol back to internal
    internalOp, ok := mgr.EQToEmu(wireOp)
    if !ok {
        panic("Wire opcode not found")
    }
    fmt.Printf("0x%04x -> %s\n", wireOp, internalOp.String())
}

Login Server Example

// Get opcodes available to login server
loginOpcodes := opcodes.LoginOpcodeSet()
fmt.Printf("Login server has %d opcodes\n", len(loginOpcodes))

// Use manager to translate login opcodes
wireOp, _ := mgr.EmuToEQ(opcodes.OP_LoginRequestMsg)

World Server Example

// Get opcodes available to world server
worldOpcodes := opcodes.WorldOpcodeSet()
fmt.Printf("World server has %d opcodes\n", len(worldOpcodes))

// Use manager to translate world opcodes
wireOp, _ := mgr.EmuToEQ(opcodes.OP_ESInitMsg)

Testing

Run Unit Tests (No Database Required)

cd go/opcodes
go test -v -short

Run All Tests (Requires Database)

cd go/opcodes
go test -v

Run Integration Tests Only

cd go/opcodes
go test -v -run Integration

Configure Database for Integration Tests

Set environment variables:

export EQ2_DB_HOST=localhost
export EQ2_DB_NAME=eq2ls
export EQ2_DB_USER=eq2
export EQ2_DB_PASS=eq2pass
go test -v

Regenerating Opcode Constants

When C++ opcode definitions change, regenerate the Go constants:

cd go/opcodes/tools
go run generate_constants.go

This reads the C++ header files:

  • /include/opcodes/emu_oplist_shared.h
  • /cmd/login/emu_oplist_login.h
  • /cmd/world/emu_oplist_world.h

And generates:

  • constants_shared.go
  • constants_login.go
  • constants_world.go
  • names.go

Design Differences from C++

Advantages

  1. No build flags: Uses LoginOpcodeSet() and WorldOpcodeSet() functions instead of preprocessor flags
  2. Dependency injection: Loader interface enables testing without database
  3. Type safety: Opcode is a distinct type, not raw int16
  4. Built-in thread safety: RWMutex instead of manual mutex management
  5. Auto-generation: Script ensures constants stay in sync with C++

Compatibility

  • Uses identical database queries as C++
  • Maintains same bidirectional translation semantics
  • Supports same version range logic (BETWEEN version_range1 AND version_range2)
  • Produces identical wire protocol values for each client version

Package API

Opcode Type

type Opcode int16

func (o Opcode) String() string         // Returns opcode name
func (o Opcode) IsValid() bool          // Checks if opcode is recognized

Manager

func NewManager(version int16) *Manager
func (m *Manager) LoadFromLoader(ctx context.Context, loader Loader) error
func (m *Manager) EmuToEQ(op Opcode) (uint16, bool)
func (m *Manager) EQToEmu(wireOp uint16) (Opcode, bool)
func (m *Manager) HasOpcode(op Opcode) bool
func (m *Manager) Version() int16
func (m *Manager) OpcodeCount() int

Registry

func NewRegistry() *Registry
func (r *Registry) LoadAllVersions(ctx context.Context, loader Loader) error
func (r *Registry) GetManager(version int16) (*Manager, bool)
func (r *Registry) VersionsLoaded() []int16
func (r *Registry) ManagerCount() int

Loader Interface

type Loader interface {
    GetVersions(ctx context.Context) ([]VersionRange, error)
    GetOpcodes(ctx context.Context, version int16) (map[string]uint16, error)
}

func NewDatabaseLoader(backend db.Backend) *DatabaseLoader

Opcode Sets

func SharedOpcodes() []Opcode      // 21 shared opcodes
func LoginOpcodes() []Opcode       // 18 login-specific opcodes
func WorldOpcodes() []Opcode       // 465 world-specific opcodes
func LoginOpcodeSet() []Opcode     // Shared + Login (39 total)
func WorldOpcodeSet() []Opcode     // Shared + World (486 total)

Database Schema

The package expects the following database table:

CREATE TABLE opcodes (
    id INT(10) PRIMARY KEY AUTO_INCREMENT,
    version_range1 SMALLINT(5) NOT NULL DEFAULT 0,
    version_range2 SMALLINT(5) NOT NULL DEFAULT 0,
    name VARCHAR(255) NOT NULL,
    opcode SMALLINT(5) NOT NULL,
    table_data_version SMALLINT(5) NOT NULL DEFAULT 1,
    UNIQUE KEY newindex (version_range1, name, version_range2)
);

License

EQ2Emu Copyright (C) 2007-2025 EQ2Emu, Sharkk See LICENSE