578 lines
16 KiB
C++
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;
|
|
} |