354 lines
13 KiB
C++
354 lines
13 KiB
C++
// Copyright (C) 2007 EQ2EMulator Development Team - GNU GPL v3 License
|
|
|
|
#pragma once
|
|
|
|
#include <cstdio>
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
#include <mutex>
|
|
|
|
#include "log.hpp"
|
|
#include "xml_parser.hpp"
|
|
#include "packet/packet_struct.hpp"
|
|
|
|
using namespace std;
|
|
|
|
class ConfigReader
|
|
{
|
|
public:
|
|
// Destructor - cleans up all allocated packet structures and vectors
|
|
~ConfigReader()
|
|
{
|
|
lock_guard<mutex> lock(m_structs_mutex);
|
|
DestroyStructs();
|
|
}
|
|
|
|
// Adds a new packet structure with specified name and version to the collection
|
|
void addStruct(const char* name, int16 version, PacketStruct* new_struct)
|
|
{
|
|
string strname(name);
|
|
vector<PacketStruct*>* struct_versions = structs[strname];
|
|
if (struct_versions) {
|
|
struct_versions->push_back(new_struct);
|
|
} else {
|
|
struct_versions = new vector<PacketStruct*>;
|
|
struct_versions->push_back(new_struct);
|
|
structs[strname] = struct_versions;
|
|
}
|
|
}
|
|
|
|
// Retrieves a packet structure by name, finding the best version match (highest version <= requested)
|
|
PacketStruct* getStruct(const char* name, int16 version)
|
|
{
|
|
PacketStruct* latest_version = 0;
|
|
PacketStruct* new_latest_version = 0;
|
|
lock_guard<mutex> lock(m_structs_mutex);
|
|
vector<PacketStruct*>* struct_versions = structs[string(name)];
|
|
|
|
if (struct_versions) {
|
|
vector<PacketStruct*>::iterator iter;
|
|
for (iter = struct_versions->begin(); iter != struct_versions->end(); iter++) {
|
|
if ((*iter)->GetVersion() <= version && (!latest_version || (*iter)->GetVersion() > latest_version->GetVersion()))
|
|
latest_version = *iter;
|
|
}
|
|
|
|
if (latest_version) {
|
|
if (latest_version->GetOpcode() != OP_Unknown && (latest_version->GetOpcodeValue(version) == 0xFFFF || latest_version->GetOpcodeValue(version)==0xCDCD)) {
|
|
LogWrite(PACKET__ERROR, 0, "Packet", "Could not find valid opcode for Packet Struct '%s' and client version %d", latest_version->GetName(), version);
|
|
} else if (strlen(latest_version->GetOpcodeType()) == 0 || latest_version->GetOpcode() != OP_Unknown) {
|
|
new_latest_version = new PacketStruct(latest_version, version);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!new_latest_version && !latest_version)
|
|
LogWrite(PACKET__ERROR, 0, "Packet", "Could not find struct named '%s' with version: %i", name, version);
|
|
return new_latest_version;
|
|
}
|
|
|
|
// Retrieves a packet structure by exact version match
|
|
PacketStruct* getStructByVersion(const char* name, int16 version)
|
|
{
|
|
PacketStruct* packet = 0;
|
|
PacketStruct* newpacket = 0;
|
|
lock_guard<mutex> lock(m_structs_mutex);
|
|
vector<PacketStruct*>* struct_versions = structs[string(name)];
|
|
|
|
if (struct_versions) {
|
|
vector<PacketStruct*>::iterator iter;
|
|
for (iter = struct_versions->begin(); iter != struct_versions->end(); iter++) {
|
|
packet = *iter;
|
|
if (packet && packet->GetVersion() == version) {
|
|
newpacket = new PacketStruct(packet, version);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!newpacket)
|
|
LogWrite(PACKET__ERROR, 0, "Packet", "Could not find struct named '%s' with version: %i", name, version);
|
|
return newpacket;
|
|
}
|
|
|
|
// Returns the version number of the best matching struct for given name and version
|
|
int16 GetStructVersion(const char* name, int16 version)
|
|
{
|
|
lock_guard<mutex> lock(m_structs_mutex);
|
|
vector<PacketStruct*>* struct_versions = structs[string(name)];
|
|
int16 ret = 0;
|
|
|
|
if (struct_versions) {
|
|
vector<PacketStruct*>::iterator iter;
|
|
PacketStruct* latest_version = 0;
|
|
for (iter = struct_versions->begin(); iter != struct_versions->end(); iter++) {
|
|
if (!latest_version || ((*iter)->GetVersion() > latest_version->GetVersion() && (*iter)->GetVersion() <= version))
|
|
latest_version = *iter;
|
|
}
|
|
if (latest_version)
|
|
ret = latest_version->GetVersion();
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
// Reloads all structures from previously loaded XML files
|
|
void ReloadStructs()
|
|
{
|
|
lock_guard<mutex> lock(m_structs_mutex);
|
|
DestroyStructs();
|
|
for (int32 i = 0; i < load_files.size(); i++)
|
|
processXML_Elements(load_files[i].c_str());
|
|
}
|
|
|
|
// Loads packet structure definitions from an XML file
|
|
bool LoadFile(const char* name)
|
|
{
|
|
load_files.push_back(name);
|
|
return processXML_Elements(name);
|
|
}
|
|
|
|
// Recursively loads data structure definitions from XML nodes into a packet structure
|
|
void loadDataStruct(PacketStruct* packet, XMLNode parentNode, bool array_packet = false)
|
|
{
|
|
for (int x = 0; x < parentNode.nChildNode(); x++) {
|
|
const char* name = parentNode.getChildNode("Data", x).getAttribute("ElementName");
|
|
const char* type = parentNode.getChildNode("Data", x).getAttribute("Type");
|
|
const char* size = parentNode.getChildNode("Data", x).getAttribute("Size");
|
|
const char* type2 = parentNode.getChildNode("Data", x).getAttribute("Type2");
|
|
const char* array_size = parentNode.getChildNode("Data", x).getAttribute("ArraySizeVariable");
|
|
const char* max_array = parentNode.getChildNode("Data", x).getAttribute("MaxArraySize");
|
|
const char* substruct = parentNode.getChildNode("Data", x).getAttribute("Substruct");
|
|
const char* default_value = parentNode.getChildNode("Data", x).getAttribute("DefaultByteValue");
|
|
const char* oversized = parentNode.getChildNode("Data", x).getAttribute("OversizedValue");
|
|
const char* oversized_byte = parentNode.getChildNode("Data", x).getAttribute("OversizedByte");
|
|
const char* if_variable = parentNode.getChildNode("Data", x).getAttribute("IfVariableSet");
|
|
const char* if_not_variable = parentNode.getChildNode("Data", x).getAttribute("IfVariableNotSet");
|
|
const char* if_equals_variable = parentNode.getChildNode("Data", x).getAttribute("IfVariableEquals");
|
|
const char* if_not_equals_variable = parentNode.getChildNode("Data", x).getAttribute("IfVariableNotEquals");
|
|
const char* optional = parentNode.getChildNode("Data", x).getAttribute("Optional");
|
|
const char* if_flag_not_set_variable = parentNode.getChildNode("Data", x).getAttribute("IfFlagNotSet");
|
|
const char* if_flag_set_variable = parentNode.getChildNode("Data", x).getAttribute("IfFlagSet");
|
|
|
|
// Parse array size with error handling
|
|
int8 max_array_size = 0;
|
|
try {
|
|
if (max_array)
|
|
max_array_size = atoi(max_array);
|
|
} catch (...) {}
|
|
|
|
// Parse element size with error handling
|
|
int16 num_size = 1;
|
|
try {
|
|
if (size)
|
|
num_size = atoi(size);
|
|
} catch (...) {}
|
|
|
|
// Parse default byte value with error handling
|
|
int8 byte_val = 0;
|
|
try {
|
|
if (default_value)
|
|
byte_val = atoi(default_value);
|
|
} catch (...) {}
|
|
|
|
// Handle substruct elements - recursively include another struct's definition
|
|
if (substruct && name) {
|
|
PacketStruct* substruct_packet = getStruct(substruct, packet->GetVersion());
|
|
if (substruct_packet) {
|
|
vector<DataStruct*>::iterator itr;
|
|
vector<DataStruct*>* structs = substruct_packet->getStructs();
|
|
DataStruct* ds = 0;
|
|
int i = 0;
|
|
char tmp[12] = {0};
|
|
|
|
// Create multiple instances of substruct if size > 1
|
|
for (i = 0; i < num_size; i++) {
|
|
snprintf(tmp, sizeof(tmp)-1, "%i", i);
|
|
for (itr = structs->begin(); itr != structs->end(); itr++) {
|
|
ds = *itr;
|
|
string new_name;
|
|
if (array_packet)
|
|
new_name = string(name).append("_").append(ds->GetStringName());
|
|
else
|
|
new_name = string(name).append("_").append(ds->GetStringName()).append("_").append(tmp);
|
|
|
|
DataStruct* ds2 = new DataStruct(new_name.c_str(), ds->GetType(), ds->GetLength(), ds->GetType2());
|
|
|
|
// Copy array size variable with proper naming
|
|
if (!array_packet && strlen(ds->GetArraySizeVariable()) > 1)
|
|
ds2->SetArraySizeVariable(string(name).append("_").append(ds->GetArraySizeVariable()).append("_").append(tmp).c_str());
|
|
|
|
// Copy all properties from original datastruct
|
|
ds2->SetOversized(ds->GetOversized());
|
|
ds2->SetOversizedByte(ds->GetOversizedByte());
|
|
ds2->SetDefaultValue(ds->GetDefaultValue());
|
|
ds2->SetMaxArraySize(ds->GetMaxArraySize());
|
|
ds2->SetIfSetVariable(ds->GetIfSetVariable() ? ds->GetIfSetVariable() : if_variable);
|
|
ds2->SetIfNotSetVariable(ds->GetIfSetVariable() ? ds->GetIfNotSetVariable() : if_not_variable);
|
|
ds2->SetIfNotEqualsVariable(ds->GetIfNotEqualsVariable());
|
|
ds2->SetIfFlagNotSetVariable(ds->GetIfFlagNotSetVariable());
|
|
ds2->SetIfFlagSetVariable(ds->GetIfFlagSetVariable());
|
|
ds2->SetIsOptional(ds->IsOptional());
|
|
ds2->AddIfSetVariable(if_variable);
|
|
ds2->AddIfNotSetVariable(if_not_variable);
|
|
packet->add(ds2);
|
|
}
|
|
}
|
|
|
|
// Handle array renaming for non-array packets
|
|
if (!array_packet) {
|
|
i--;
|
|
substruct_packet->renameSubstructArray(name, i);
|
|
packet->addPacketArrays(substruct_packet);
|
|
}
|
|
|
|
delete substruct_packet;
|
|
}
|
|
continue;
|
|
} else if (type && strncasecmp(type,"Array", 5)==0 && array_size) {
|
|
// Handle array type elements - create subpacket for array structure
|
|
PacketStruct* new_packet = new PacketStruct;
|
|
new_packet->SetName(name);
|
|
new_packet->IsSubPacket(true);
|
|
new_packet->SetVersion(packet->GetVersion());
|
|
loadDataStruct(new_packet, parentNode.getChildNode("Data", x), true);
|
|
packet->add(new_packet);
|
|
}
|
|
|
|
// Validate required attributes
|
|
if (!name || !type) {
|
|
LogWrite(MISC__WARNING, 0, "Misc", "Ignoring invalid Data Element, all elements must include at least an ElementName and Type!");
|
|
LogWrite(MISC__WARNING, 0, "Misc", "\tStruct: '%s', version: %i", parentNode.getAttribute("Name"), parentNode.getAttribute("ClientVersion"));
|
|
continue;
|
|
}
|
|
|
|
// Create new data structure element
|
|
DataStruct* ds = new DataStruct(name, type, num_size, type2);
|
|
|
|
// Parse oversized values with error handling
|
|
int8 oversized_value = 0;
|
|
int8 oversized_byte_value = 255;
|
|
if (oversized) {
|
|
try {
|
|
oversized_value = atoi(oversized);
|
|
} catch (...) {}
|
|
}
|
|
if (oversized_byte) {
|
|
try {
|
|
oversized_byte_value = atoi(oversized_byte);
|
|
} catch (...) {}
|
|
}
|
|
|
|
// Set all properties on the data structure
|
|
ds->SetOversizedByte(oversized_byte_value);
|
|
ds->SetOversized(oversized_value);
|
|
ds->SetMaxArraySize(max_array_size);
|
|
if (array_size)
|
|
ds->SetArraySizeVariable(array_size);
|
|
ds->SetDefaultValue(byte_val);
|
|
ds->SetIfSetVariable(if_variable);
|
|
ds->SetIfNotSetVariable(if_not_variable);
|
|
ds->SetIfEqualsVariable(if_equals_variable);
|
|
ds->SetIfNotEqualsVariable(if_not_equals_variable);
|
|
ds->SetIfFlagNotSetVariable(if_flag_not_set_variable);
|
|
ds->SetIfFlagSetVariable(if_flag_set_variable);
|
|
|
|
// Parse optional flag
|
|
if (optional && strlen(optional) > 0 && (strcmp("true", optional) == 0 || strcmp("TRUE", optional) == 0 || strcmp("True", optional) == 0))
|
|
ds->SetIsOptional(true);
|
|
|
|
packet->add(ds);
|
|
}
|
|
}
|
|
|
|
private:
|
|
// Parses XML file and loads all struct definitions into memory
|
|
bool processXML_Elements(const char* fileName)
|
|
{
|
|
XMLNode xMainNode = XMLNode::openFileHelper(fileName,"EQ2Emulator");
|
|
if (xMainNode.isEmpty())
|
|
return false;
|
|
|
|
// Process each struct definition in the XML file
|
|
for (int i = 0; i < xMainNode.nChildNode("Struct"); i++) {
|
|
const char* struct_name = xMainNode.getChildNode("Struct", i).getAttribute("Name");
|
|
const char* str_version = xMainNode.getChildNode("Struct", i).getAttribute("ClientVersion");
|
|
const char* opcode_name = xMainNode.getChildNode("Struct", i).getAttribute("OpcodeName");
|
|
const char* opcode_type = xMainNode.getChildNode("Struct", i).getAttribute("OpcodeType");
|
|
|
|
// Validate required attributes
|
|
if (!struct_name || !str_version) {
|
|
LogWrite(MISC__WARNING, 0, "Misc", "Ignoring invalid struct, all structs must include at least a Name and ClientVersion!");
|
|
continue;
|
|
}
|
|
|
|
// Parse version number with error handling
|
|
int16 version = 1;
|
|
try {
|
|
version = atoi(str_version);
|
|
} catch (...) {
|
|
LogWrite(MISC__WARNING, 0, "Misc", "Ignoring invalid version for struct named '%s': '%s'", struct_name, str_version);
|
|
continue;
|
|
}
|
|
|
|
// Create new packet structure
|
|
PacketStruct* new_struct = new PacketStruct();
|
|
new_struct->SetName(struct_name);
|
|
if (opcode_type)
|
|
new_struct->SetOpcodeType(opcode_type);
|
|
if (opcode_name) {
|
|
if (!new_struct->SetOpcode(opcode_name)) {
|
|
delete new_struct;
|
|
continue;
|
|
}
|
|
}
|
|
new_struct->SetVersion(version);
|
|
|
|
// Load all data elements for this struct
|
|
loadDataStruct(new_struct, xMainNode.getChildNode("Struct", i));
|
|
addStruct(struct_name, version, new_struct);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Destroys all allocated packet structures and clears collections
|
|
void DestroyStructs()
|
|
{
|
|
map<string, vector<PacketStruct*>*>::iterator struct_iterator;
|
|
for (struct_iterator = structs.begin(); struct_iterator != structs.end(); struct_iterator++) {
|
|
vector<PacketStruct*>* versions = struct_iterator->second;
|
|
vector<PacketStruct*>::iterator version_iter;
|
|
if (versions) {
|
|
for (version_iter = versions->begin(); version_iter != versions->end(); version_iter++) {
|
|
delete *version_iter;
|
|
}
|
|
}
|
|
delete versions;
|
|
}
|
|
structs.clear();
|
|
}
|
|
|
|
mutex m_structs_mutex; // Thread synchronization for struct access
|
|
vector<string> load_files; // List of loaded XML files for reloading
|
|
map<string, vector<PacketStruct*>*> structs; // Map of struct names to version vectors
|
|
}; |