// EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - GPL v3 #pragma once #include #include #include #include #include #include #include "emu_opcodes.hpp" #include "../debug.hpp" #include "../types.hpp" #include "../log.hpp" #if defined(SHARED_OPCODES) && !defined(EQ2) #include "EMuShareMem.h" extern LoadEMuShareMemDLL EMuShareMemDLL; #endif using namespace std; // Base opcode manager class providing common functionality for opcode translation class OpcodeManager { public: // Constructor initializes the manager to an unloaded state OpcodeManager(); virtual ~OpcodeManager() {} // Returns whether this manager supports opcode modification at runtime virtual bool Mutable() { return false; } // Load opcodes from a file containing opcode definitions virtual bool LoadOpcodes(const char *filename) = 0; // Load opcodes from a pre-parsed map of opcode names to values virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr) = 0; // Reload opcodes from file, typically used for runtime updates virtual bool ReloadOpcodes(const char *filename) = 0; // Convert emulator opcode to EverQuest client opcode virtual uint16 EmuToEQ(const EmuOpcode emu_op) = 0; // Convert EverQuest client opcode to emulator opcode virtual EmuOpcode EQToEmu(const uint16 eq_op) = 0; // Convert emulator opcode to human-readable name static const char *EmuToName(const EmuOpcode emu_op); // Convert EverQuest opcode to human-readable name via emulator opcode lookup const char *EQToName(const uint16 emu_op); // Find emulator opcode by name (case-insensitive search) EmuOpcode NameSearch(const char *name); // Strategy pattern interface for setting opcodes during loading class OpcodeSetStrategy { public: // Set mapping between emulator and EverQuest opcodes virtual void Set(EmuOpcode emu_op, uint16 eq_op) = 0; virtual ~OpcodeSetStrategy() {} }; protected: bool loaded; // True when opcodes have been successfully loaded std::mutex MOpcodes; // Mutex protecting opcode data structures // Load opcodes from file using specified strategy for storage static bool LoadOpcodesFile(const char *filename, OpcodeSetStrategy *s); // Load opcodes from map using specified strategy for storage static bool LoadOpcodesMap(map* eq, OpcodeSetStrategy *s, std::string* missingOpcodes = nullptr); }; // Opcode manager that supports runtime modification of opcode mappings class MutableOpcodeManager : public OpcodeManager { public: MutableOpcodeManager() : OpcodeManager() {} // Returns true indicating this manager supports runtime modifications virtual bool Mutable() { return true; } // Set individual opcode mapping at runtime virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op) = 0; }; #ifdef SHARED_OPCODES // Opcode manager using shared memory for inter-process opcode sharing class SharedOpcodeManager : public OpcodeManager { public: virtual ~SharedOpcodeManager() {} // Load opcodes from file into shared memory virtual bool LoadOpcodes(const char *filename); // Load opcodes from map into shared memory virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); // Reload opcodes from file into shared memory virtual bool ReloadOpcodes(const char *filename); // Convert emulator opcode to EverQuest opcode using shared memory lookup virtual uint16 EmuToEQ(const EmuOpcode emu_op); // Convert EverQuest opcode to emulator opcode using shared memory lookup virtual EmuOpcode EQToEmu(const uint16 eq_op); protected: // Strategy for storing opcodes in shared memory class SharedMemStrategy : public OpcodeManager::OpcodeSetStrategy { public: // Store opcode mapping in shared memory void Set(EmuOpcode emu_op, uint16 eq_op); }; // Callback for DLL-based opcode loading static bool DLLLoadOpcodesCallback(const char *filename); }; #endif // Standard opcode manager using heap memory for opcode storage class RegularOpcodeManager : public MutableOpcodeManager { public: // Constructor allocates memory for opcode translation tables RegularOpcodeManager(); // Destructor cleans up allocated memory virtual ~RegularOpcodeManager(); // Returns true indicating opcodes can be edited virtual bool Editable() { return true; } // Load opcodes from file into memory arrays virtual bool LoadOpcodes(const char *filename); // Load opcodes from map into memory arrays virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); // Reload opcodes from file, preserving existing mappings where possible virtual bool ReloadOpcodes(const char *filename); // Convert emulator opcode to EverQuest opcode using array lookup virtual uint16 EmuToEQ(const EmuOpcode emu_op); // Convert EverQuest opcode to emulator opcode using array lookup virtual EmuOpcode EQToEmu(const uint16 eq_op); // Set individual opcode mapping, updating both translation arrays virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op); protected: // Strategy for storing opcodes in regular memory arrays class NormalMemStrategy : public OpcodeManager::OpcodeSetStrategy { public: RegularOpcodeManager *it; // Pointer to parent manager // Store opcode mapping in memory arrays void Set(EmuOpcode emu_op, uint16 eq_op); }; friend class NormalMemStrategy; private: uint16 *emu_to_eq; // Array mapping emulator opcodes to EverQuest opcodes EmuOpcode *eq_to_emu; // Array mapping EverQuest opcodes to emulator opcodes uint32 EQOpcodeCount; // Size of EverQuest opcode array uint32 EmuOpcodeCount; // Size of emulator opcode array }; // Null opcode manager that always returns default values (for testing) class NullOpcodeManager : public MutableOpcodeManager { public: NullOpcodeManager(); // Always succeeds but loads no actual opcodes virtual bool LoadOpcodes(const char *filename); // Always succeeds but loads no actual opcodes virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); // Always succeeds but reloads no actual opcodes virtual bool ReloadOpcodes(const char *filename); // Always returns 0 (invalid opcode) virtual uint16 EmuToEQ(const EmuOpcode emu_op); // Always returns OP_Unknown virtual EmuOpcode EQToEmu(const uint16 eq_op); // No-op implementation for testing compatibility virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op) {} }; // Empty opcode manager that starts with no mappings but remembers what is set class EmptyOpcodeManager : public MutableOpcodeManager { public: EmptyOpcodeManager(); // No-op load operation - manager starts empty virtual bool LoadOpcodes(const char *filename); // No-op load operation - manager starts empty virtual bool LoadOpcodes(map* eq, std::string* missingOpcodes = nullptr); // No-op reload operation virtual bool ReloadOpcodes(const char *filename); // Lookup emulator to EverQuest opcode in stored mappings virtual uint16 EmuToEQ(const EmuOpcode emu_op); // Lookup EverQuest to emulator opcode in stored mappings virtual EmuOpcode EQToEmu(const uint16 eq_op); // Store opcode mapping in internal maps virtual void SetOpcode(EmuOpcode emu_op, uint16 eq_op); private: map emu_to_eq; // Map of emulator to EverQuest opcodes map eq_to_emu; // Map of EverQuest to emulator opcodes }; // Implementation // Initialize opcode manager in unloaded state OpcodeManager::OpcodeManager() { loaded = false; } // Load opcodes from map using specified storage strategy bool OpcodeManager::LoadOpcodesMap(map* eq, OpcodeSetStrategy *s, std::string* missingOpcodes) { bool ret = true; EmuOpcode emu_op; map::iterator res; // Iterate through all emulator opcodes to build translation mappings for(emu_op = (EmuOpcode)(0); emu_op < _maxEmuOpcode; emu_op=(EmuOpcode)(emu_op+1)) { // Get human-readable name for this emulator opcode const char *op_name = OpcodeNames[emu_op]; if(op_name[0] == '\0') { break; } // Find corresponding EverQuest opcode in the provided map res = eq->find(op_name); if(res == eq->end()) { // Handle missing opcode by either logging or collecting for batch report if(missingOpcodes) { if(missingOpcodes->size() < 1) { missingOpcodes->append(op_name); } else { missingOpcodes->append(", " + std::string(op_name)); } } else { LogWrite(OPCODE__WARNING, 1, "Opcode", "Opcode %s is missing from the opcodes table.", op_name); } // Set invalid mapping to indicate missing opcode s->Set(emu_op, 0xFFFF); continue; } // Store valid mapping using the provided strategy s->Set(emu_op, res->second); } return ret; } // Parse opcode file and load mappings using specified storage strategy bool OpcodeManager::LoadOpcodesFile(const char *filename, OpcodeSetStrategy *s) { FILE *opf = fopen(filename, "r"); if(opf == NULL) { LogWrite(OPCODE__ERROR, 0, "Opcode", "Unable to open opcodes file '%s'. Thats bad.", filename); return false; } map eq; // Parse the opcode file line by line char line[2048]; int lineno = 0; uint16 curop; while(!feof(opf)) { lineno++; line[0] = '\0'; if(fgets(line, sizeof(line), opf) == NULL) break; // Skip lines that don't start with OP_ (comments, blank lines, etc.) if(line[0] != 'O' || line[1] != 'P' || line[2] != '_') continue; char *num = line+3; // Find the equals sign separating name from value while(*num != '=' && *num != '\0') { num++; } // Ensure we found a proper assignment if(*num != '=') { LogWrite(OPCODE__ERROR, 0, "Opcode", "Malformed opcode line at %s:%d\n", filename, lineno); continue; } *num = '\0'; // Null terminate the opcode name num++; // Point to the opcode value // Parse hexadecimal opcode value if(sscanf(num, "0x%hx", &curop) != 1) { LogWrite(OPCODE__ERROR, 0, "Opcode", "Malformed opcode at %s:%d\n", filename, lineno); continue; } // Store parsed opcode mapping in temporary map eq[line] = curop; } fclose(opf); return LoadOpcodesMap(&eq, s); } // Convert emulator opcode to human-readable name const char *OpcodeManager::EmuToName(const EmuOpcode emu_op) { if(emu_op > _maxEmuOpcode) return "OP_Unknown"; return OpcodeNames[emu_op]; } // Convert EverQuest opcode to human-readable name via emulator opcode lookup const char *OpcodeManager::EQToName(const uint16 eq_op) { // First resolve EverQuest opcode to emulator opcode EmuOpcode emu_op = EQToEmu(eq_op); if(emu_op > _maxEmuOpcode) return "OP_Unknown"; return OpcodeNames[emu_op]; } // Search for emulator opcode by name (case-insensitive) EmuOpcode OpcodeManager::NameSearch(const char *name) { EmuOpcode emu_op; // Linear search through all opcode names for(emu_op = (EmuOpcode)(0); emu_op < _maxEmuOpcode; emu_op=(EmuOpcode)(emu_op+1)) { const char *op_name = OpcodeNames[emu_op]; if(!strcasecmp(op_name, name)) { return emu_op; } } return OP_Unknown; } // Initialize regular opcode manager with null arrays RegularOpcodeManager::RegularOpcodeManager() : MutableOpcodeManager() { emu_to_eq = nullptr; eq_to_emu = nullptr; EQOpcodeCount = 0; EmuOpcodeCount = 0; } // Clean up allocated translation arrays RegularOpcodeManager::~RegularOpcodeManager() { delete[] emu_to_eq; delete[] eq_to_emu; } // Load opcodes from map into memory arrays bool RegularOpcodeManager::LoadOpcodes(map* eq, std::string* missingOpcodes) { NormalMemStrategy s; s.it = this; std::lock_guard lock(MOpcodes); loaded = true; eq_to_emu = new EmuOpcode[MAX_EQ_OPCODE]; emu_to_eq = new uint16[_maxEmuOpcode]; EQOpcodeCount = MAX_EQ_OPCODE; EmuOpcodeCount = _maxEmuOpcode; // Initialize arrays with default values memset(eq_to_emu, 0, sizeof(EmuOpcode)*MAX_EQ_OPCODE); memset(emu_to_eq, 0xCD, sizeof(uint16)*_maxEmuOpcode); bool ret = LoadOpcodesMap(eq, &s, missingOpcodes); return ret; } // Load opcodes from file into memory arrays bool RegularOpcodeManager::LoadOpcodes(const char *filename) { NormalMemStrategy s; s.it = this; std::lock_guard lock(MOpcodes); loaded = true; eq_to_emu = new EmuOpcode[MAX_EQ_OPCODE]; emu_to_eq = new uint16[_maxEmuOpcode]; EQOpcodeCount = MAX_EQ_OPCODE; EmuOpcodeCount = _maxEmuOpcode; // Initialize arrays with default values memset(eq_to_emu, 0, sizeof(EmuOpcode)*MAX_EQ_OPCODE); memset(emu_to_eq, 0xCD, sizeof(uint16)*_maxEmuOpcode); bool ret = LoadOpcodesFile(filename, &s); return ret; } // Reload opcodes from file, preserving existing array allocations bool RegularOpcodeManager::ReloadOpcodes(const char *filename) { if(!loaded) return LoadOpcodes(filename); NormalMemStrategy s; s.it = this; std::lock_guard lock(MOpcodes); // Clear existing EverQuest to emulator mappings memset(eq_to_emu, 0, sizeof(EmuOpcode)*MAX_EQ_OPCODE); bool ret = LoadOpcodesFile(filename, &s); return ret; } // Convert emulator opcode to EverQuest opcode using array lookup uint16 RegularOpcodeManager::EmuToEQ(const EmuOpcode emu_op) { uint16 res; std::lock_guard lock(MOpcodes); if(emu_op > _maxEmuOpcode) res = 0; else res = emu_to_eq[emu_op]; return res; } // Convert EverQuest opcode to emulator opcode using array lookup EmuOpcode RegularOpcodeManager::EQToEmu(const uint16 eq_op) { EmuOpcode res; std::lock_guard lock(MOpcodes); if(eq_op >= MAX_EQ_OPCODE) res = OP_Unknown; else res = eq_to_emu[eq_op]; return res; } // Set individual opcode mapping, updating both translation arrays void RegularOpcodeManager::SetOpcode(EmuOpcode emu_op, uint16 eq_op) { // Clear existing mapping for this emulator opcode uint16 oldop = 0; if(emu_op <= _maxEmuOpcode) oldop = emu_to_eq[emu_op]; if(oldop != 0 && oldop < MAX_EQ_OPCODE) eq_to_emu[oldop] = OP_Unknown; // Set new mapping using our strategy NormalMemStrategy s; s.it = this; s.Set(emu_op, eq_op); } // Store opcode mapping in memory arrays void RegularOpcodeManager::NormalMemStrategy::Set(EmuOpcode emu_op, uint16 eq_op) { if(uint32(emu_op) >= it->EmuOpcodeCount || eq_op >= it->EQOpcodeCount) return; it->emu_to_eq[emu_op] = eq_op; it->eq_to_emu[eq_op] = emu_op; } // Initialize null opcode manager NullOpcodeManager::NullOpcodeManager() : MutableOpcodeManager() { } // No-op load from map - always succeeds but loads nothing bool NullOpcodeManager::LoadOpcodes(map* eq, std::string* missingOpcodes) { return true; } // No-op load from file - always succeeds but loads nothing bool NullOpcodeManager::LoadOpcodes(const char *filename) { return true; } // No-op reload - always succeeds but reloads nothing bool NullOpcodeManager::ReloadOpcodes(const char *filename) { return true; } // Always return invalid opcode uint16 NullOpcodeManager::EmuToEQ(const EmuOpcode emu_op) { return 0; } // Always return unknown opcode EmuOpcode NullOpcodeManager::EQToEmu(const uint16 eq_op) { return OP_Unknown; } // Initialize empty opcode manager EmptyOpcodeManager::EmptyOpcodeManager() : MutableOpcodeManager() { } // No-op load from file - manager starts empty bool EmptyOpcodeManager::LoadOpcodes(const char *filename) { return true; } // No-op load from map - manager starts empty bool EmptyOpcodeManager::LoadOpcodes(map* eq, std::string* missingOpcodes) { return true; } // No-op reload operation bool EmptyOpcodeManager::ReloadOpcodes(const char *filename) { return true; } // Lookup emulator to EverQuest opcode in stored mappings uint16 EmptyOpcodeManager::EmuToEQ(const EmuOpcode emu_op) { map::iterator f; f = emu_to_eq.find(emu_op); return (f == emu_to_eq.end() ? 0 : f->second); } // Lookup EverQuest to emulator opcode in stored mappings EmuOpcode EmptyOpcodeManager::EQToEmu(const uint16 eq_op) { map::iterator f; f = eq_to_emu.find(eq_op); return (f == eq_to_emu.end() ? OP_Unknown : f->second); } // Store opcode mapping in internal maps void EmptyOpcodeManager::SetOpcode(EmuOpcode emu_op, uint16 eq_op) { emu_to_eq[emu_op] = eq_op; eq_to_emu[eq_op] = emu_op; }