1
0
Protocol/opcodes.go

272 lines
8.5 KiB
Go

package eq2net
import (
"fmt"
"sync"
)
// VersionRange represents a client version range mapped to an opcode version
type VersionRange struct {
MinVersion uint16 // version_range1 in database
MaxVersion uint16 // version_range2 in database
OpcodeVersion uint16 // The opcode version for this range (usually MinVersion)
}
// OpcodeVersionMap maps version ranges for opcode lookups
// This replaces the C++ EQOpcodeVersions map<int16, int16>
type OpcodeVersionMap map[uint16]uint16 // Key: version_range1, Value: version_range2
// GetOpcodeVersion returns the opcode version for a given client version
// This is a direct port of the C++ GetOpcodeVersion function
func GetOpcodeVersion(clientVersion uint16, versionMap OpcodeVersionMap) uint16 {
ret := clientVersion
// Iterate through version ranges to find a match
for minVersion, maxVersion := range versionMap {
if clientVersion >= minVersion && clientVersion <= maxVersion {
ret = minVersion
break
}
}
return ret
}
// EmuOpcode represents an emulator-side opcode
type EmuOpcode uint16
// Common emulator opcodes - these match the C++ emu_opcodes.h
const (
OP_Unknown EmuOpcode = 0x0000
OP_LoginRequestMsg EmuOpcode = 0x0001
OP_LoginByNumRequestMsg EmuOpcode = 0x0002
OP_WSLoginRequestMsg EmuOpcode = 0x0003
OP_ESLoginRequestMsg EmuOpcode = 0x0004
OP_LoginReplyMsg EmuOpcode = 0x0005
OP_WSStatusReplyMsg EmuOpcode = 0x0006
OP_WorldListMsg EmuOpcode = 0x0007
OP_WorldStatusMsg EmuOpcode = 0x0008
OP_DeleteCharacterRequestMsg EmuOpcode = 0x0009
OP_DeleteCharacterReplyMsg EmuOpcode = 0x000A
OP_CreateCharacterRequestMsg EmuOpcode = 0x000B
OP_CreateCharacterReplyMsg EmuOpcode = 0x000C
OP_PlayCharacterRequestMsg EmuOpcode = 0x000D
OP_PlayCharacterReplyMsg EmuOpcode = 0x000E
OP_ServerListRequestMsg EmuOpcode = 0x000F
OP_ServerListReplyMsg EmuOpcode = 0x0010
OP_CharacterListRequestMsg EmuOpcode = 0x0011
OP_CharacterListReplyMsg EmuOpcode = 0x0012
// Add more opcodes as needed
)
// OpcodeNames maps emulator opcodes to their string names
// This matches the C++ OpcodeNames array
var OpcodeNames = map[EmuOpcode]string{
OP_Unknown: "OP_Unknown",
OP_LoginRequestMsg: "OP_LoginRequestMsg",
OP_LoginByNumRequestMsg: "OP_LoginByNumRequestMsg",
OP_WSLoginRequestMsg: "OP_WSLoginRequestMsg",
OP_ESLoginRequestMsg: "OP_ESLoginRequestMsg",
OP_LoginReplyMsg: "OP_LoginReplyMsg",
OP_WSStatusReplyMsg: "OP_WSStatusReplyMsg",
OP_WorldListMsg: "OP_WorldListMsg",
OP_WorldStatusMsg: "OP_WorldStatusMsg",
OP_DeleteCharacterRequestMsg: "OP_DeleteCharacterRequestMsg",
OP_DeleteCharacterReplyMsg: "OP_DeleteCharacterReplyMsg",
OP_CreateCharacterRequestMsg: "OP_CreateCharacterRequestMsg",
OP_CreateCharacterReplyMsg: "OP_CreateCharacterReplyMsg",
OP_PlayCharacterRequestMsg: "OP_PlayCharacterRequestMsg",
OP_PlayCharacterReplyMsg: "OP_PlayCharacterReplyMsg",
OP_ServerListRequestMsg: "OP_ServerListRequestMsg",
OP_ServerListReplyMsg: "OP_ServerListReplyMsg",
OP_CharacterListRequestMsg: "OP_CharacterListRequestMsg",
OP_CharacterListReplyMsg: "OP_CharacterListReplyMsg",
}
// RegularOpcodeManager manages opcode mappings for a specific version
// This is equivalent to the C++ RegularOpcodeManager class
type RegularOpcodeManager struct {
version uint16
emuToEQ map[EmuOpcode]uint16
eqToEmu map[uint16]EmuOpcode
mu sync.RWMutex
}
// NewRegularOpcodeManager creates a new opcode manager
func NewRegularOpcodeManager(version uint16) *RegularOpcodeManager {
return &RegularOpcodeManager{
version: version,
emuToEQ: make(map[EmuOpcode]uint16),
eqToEmu: make(map[uint16]EmuOpcode),
}
}
// LoadOpcodes loads opcode mappings from a map
// Input format matches database: map[opcode_name]opcode_value
func (om *RegularOpcodeManager) LoadOpcodes(opcodes map[string]uint16) bool {
om.mu.Lock()
defer om.mu.Unlock()
// Clear existing mappings
om.emuToEQ = make(map[EmuOpcode]uint16)
om.eqToEmu = make(map[uint16]EmuOpcode)
// Build bidirectional mappings
for name, eqOpcode := range opcodes {
// Find the emulator opcode by name
var emuOpcode EmuOpcode = OP_Unknown
for emu, opcName := range OpcodeNames {
if opcName == name {
emuOpcode = emu
break
}
}
if emuOpcode != OP_Unknown {
om.emuToEQ[emuOpcode] = eqOpcode
om.eqToEmu[eqOpcode] = emuOpcode
}
}
return true
}
// EmuToEQ converts an emulator opcode to EQ network opcode
func (om *RegularOpcodeManager) EmuToEQ(emu EmuOpcode) uint16 {
om.mu.RLock()
defer om.mu.RUnlock()
if eq, exists := om.emuToEQ[emu]; exists {
return eq
}
return 0xCDCD // Invalid opcode marker (matches C++)
}
// EQToEmu converts an EQ network opcode to emulator opcode
func (om *RegularOpcodeManager) EQToEmu(eq uint16) EmuOpcode {
om.mu.RLock()
defer om.mu.RUnlock()
if emu, exists := om.eqToEmu[eq]; exists {
return emu
}
return OP_Unknown
}
// EmuToName returns the name of an emulator opcode
func (om *RegularOpcodeManager) EmuToName(emu EmuOpcode) string {
if name, exists := OpcodeNames[emu]; exists {
return name
}
return "OP_Unknown"
}
// EQToName returns the name of an EQ network opcode
func (om *RegularOpcodeManager) EQToName(eq uint16) string {
emu := om.EQToEmu(eq)
return om.EmuToName(emu)
}
// NameSearch finds an emulator opcode by name
func NameSearch(name string) EmuOpcode {
for opcode, opcName := range OpcodeNames {
if opcName == name {
return opcode
}
}
return OP_Unknown
}
// EQOpcodeManager is the global opcode manager map
// Maps opcode version to manager instance
// This replaces the C++ map<int16, OpcodeManager*> EQOpcodeManager
type EQOpcodeManagerMap map[uint16]*RegularOpcodeManager
// NewEQOpcodeManager creates and initializes the global opcode manager
func NewEQOpcodeManager() EQOpcodeManagerMap {
return make(EQOpcodeManagerMap)
}
// LoadFromDatabase simulates loading opcodes from database results
// This would be called by your application after querying the database
func (m EQOpcodeManagerMap) LoadFromDatabase(versions OpcodeVersionMap, opcodesByVersion map[uint16]map[string]uint16) error {
// For each version range, create an opcode manager
for minVersion := range versions {
manager := NewRegularOpcodeManager(minVersion)
// Load opcodes for this version
if opcodes, exists := opcodesByVersion[minVersion]; exists {
if !manager.LoadOpcodes(opcodes) {
return fmt.Errorf("failed to load opcodes for version %d", minVersion)
}
} else {
return fmt.Errorf("no opcodes found for version %d", minVersion)
}
m[minVersion] = manager
}
return nil
}
// GetManagerForClient returns the appropriate opcode manager for a client version
func (m EQOpcodeManagerMap) GetManagerForClient(clientVersion uint16, versionMap OpcodeVersionMap) *RegularOpcodeManager {
opcodeVersion := GetOpcodeVersion(clientVersion, versionMap)
return m[opcodeVersion]
}
// Example helper functions for database integration
// These would be implemented by the application using this library
// LoadVersionsFromDB would execute:
// SELECT DISTINCT version_range1, version_range2 FROM opcodes
func LoadVersionsFromDB() OpcodeVersionMap {
// This is just an example - actual implementation would query the database
return OpcodeVersionMap{
1: 546, // Version range 1-546 uses opcode version 1
547: 889, // Version range 547-889 uses opcode version 547
890: 1027, // etc.
1028: 1048,
1049: 1095,
1096: 1184,
1185: 1197,
1198: 1207,
1208: 1211,
1212: 9999,
}
}
// LoadOpcodesFromDB would execute:
// SELECT name, opcode FROM opcodes WHERE ? BETWEEN version_range1 AND version_range2
func LoadOpcodesFromDB(version uint16) map[string]uint16 {
// This is just an example - actual implementation would query the database
return map[string]uint16{
"OP_LoginRequestMsg": 0x00B3,
"OP_LoginReplyMsg": 0x00B6,
// ... etc
}
}
// InitializeOpcodeSystem shows how to initialize the opcode system
// This would be called during server startup
func InitializeOpcodeSystem() (EQOpcodeManagerMap, OpcodeVersionMap, error) {
// Load version ranges from database
versions := LoadVersionsFromDB()
// Create the global opcode manager
opcodeManager := NewEQOpcodeManager()
// Load opcodes for each version
opcodesByVersion := make(map[uint16]map[string]uint16)
for minVersion := range versions {
opcodes := LoadOpcodesFromDB(minVersion)
opcodesByVersion[minVersion] = opcodes
}
// Initialize the manager
if err := opcodeManager.LoadFromDatabase(versions, opcodesByVersion); err != nil {
return nil, nil, err
}
return opcodeManager, versions, nil
}