272 lines
8.5 KiB
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
|
|
} |