eq2go/old/common/opcodes/opcode_manager.hpp
2025-08-06 19:00:30 -05:00

578 lines
16 KiB
C++

// EQ2Emulator: Everquest II Server Emulator - Copyright (C) 2007 EQ2EMulator Development Team (http://www.eq2emulator.net) - GPL v3
#pragma once
#include <cstdio>
#include <cstring>
#include <map>
#include <string>
#include <mutex>
#include <memory>
#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<string, uint16>* 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<string, uint16>* 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<string, uint16>* 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<string, uint16>* 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<string, uint16>* 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<string, uint16>* 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<EmuOpcode, uint16> emu_to_eq; // Map of emulator to EverQuest opcodes
map<uint16, EmuOpcode> 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<string, uint16>* eq, OpcodeSetStrategy *s, std::string* missingOpcodes)
{
bool ret = true;
EmuOpcode emu_op;
map<string, uint16>::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<string, uint16> 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<string, uint16>* eq, std::string* missingOpcodes)
{
NormalMemStrategy s;
s.it = this;
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<string, uint16>* 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<string, uint16>* 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<EmuOpcode, uint16>::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<uint16, EmuOpcode>::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;
}