#include "game/opcode_table.hpp" #include "core/logger.hpp" #include #include #include #include #include #include #include namespace wowee { namespace game { // Global active opcode table pointer static const OpcodeTable* g_activeOpcodeTable = nullptr; void setActiveOpcodeTable(const OpcodeTable* table) { g_activeOpcodeTable = table; } const OpcodeTable* getActiveOpcodeTable() { return g_activeOpcodeTable; } // Name ↔ LogicalOpcode mapping table (generated from the enum) struct OpcodeNameEntry { const char* name; LogicalOpcode op; }; // Expansion/core naming aliases -> canonical LogicalOpcode names used by implementation. struct OpcodeAliasEntry { const char* alias; const char* canonical; }; // clang-format off static const OpcodeAliasEntry kOpcodeAliases[] = { #include "game/opcode_aliases_generated.inc" }; static const OpcodeNameEntry kOpcodeNames[] = { #include "game/opcode_names_generated.inc" }; // clang-format on static constexpr size_t kOpcodeNameCount = sizeof(kOpcodeNames) / sizeof(kOpcodeNames[0]); static constexpr size_t kOpcodeAliasCount = sizeof(kOpcodeAliases) / sizeof(kOpcodeAliases[0]); static std::string_view canonicalOpcodeName(std::string_view name) { for (size_t i = 0; i < kOpcodeAliasCount; ++i) { if (name == kOpcodeAliases[i].alias) return kOpcodeAliases[i].canonical; } return name; } static std::optional resolveLogicalOpcodeIndex(std::string_view name) { const std::string_view canonical = canonicalOpcodeName(name); for (size_t i = 0; i < kOpcodeNameCount; ++i) { if (canonical == kOpcodeNames[i].name) { return static_cast(kOpcodeNames[i].op); } } return std::nullopt; } static std::optional parseStringField(const std::string& json, const char* fieldName) { const std::string needle = std::string("\"") + fieldName + "\""; size_t keyPos = json.find(needle); if (keyPos == std::string::npos) return std::nullopt; size_t colon = json.find(':', keyPos + needle.size()); if (colon == std::string::npos) return std::nullopt; size_t valueStart = json.find('"', colon + 1); if (valueStart == std::string::npos) return std::nullopt; size_t valueEnd = json.find('"', valueStart + 1); if (valueEnd == std::string::npos) return std::nullopt; return json.substr(valueStart + 1, valueEnd - valueStart - 1); } static std::vector parseStringArrayField(const std::string& json, const char* fieldName) { std::vector values; const std::string needle = std::string("\"") + fieldName + "\""; size_t keyPos = json.find(needle); if (keyPos == std::string::npos) return values; size_t colon = json.find(':', keyPos + needle.size()); if (colon == std::string::npos) return values; size_t arrayStart = json.find('[', colon + 1); if (arrayStart == std::string::npos) return values; size_t arrayEnd = json.find(']', arrayStart + 1); if (arrayEnd == std::string::npos) return values; size_t pos = arrayStart + 1; while (pos < arrayEnd) { size_t valueStart = json.find('"', pos); if (valueStart == std::string::npos || valueStart >= arrayEnd) break; size_t valueEnd = json.find('"', valueStart + 1); if (valueEnd == std::string::npos || valueEnd > arrayEnd) break; values.push_back(json.substr(valueStart + 1, valueEnd - valueStart - 1)); pos = valueEnd + 1; } return values; } static bool loadOpcodeJsonRecursive(const std::filesystem::path& path, std::unordered_map& logicalToWire, std::unordered_map& wireToLogical, std::unordered_set& loadingStack) { const std::filesystem::path canonicalPath = std::filesystem::weakly_canonical(path); const std::string canonicalKey = canonicalPath.string(); if (!loadingStack.insert(canonicalKey).second) { LOG_WARNING("OpcodeTable: inheritance cycle at ", canonicalKey); return false; } std::ifstream f(canonicalPath); if (!f.is_open()) { LOG_WARNING("OpcodeTable: cannot open ", canonicalPath.string()); loadingStack.erase(canonicalKey); return false; } std::string json((std::istreambuf_iterator(f)), std::istreambuf_iterator()); bool ok = true; if (auto extends = parseStringField(json, "_extends")) { ok = loadOpcodeJsonRecursive(canonicalPath.parent_path() / *extends, logicalToWire, wireToLogical, loadingStack) && ok; } for (const std::string& removeName : parseStringArrayField(json, "_remove")) { auto logical = resolveLogicalOpcodeIndex(removeName); if (!logical) continue; auto it = logicalToWire.find(*logical); if (it != logicalToWire.end()) { const uint16_t oldWire = it->second; logicalToWire.erase(it); auto wireIt = wireToLogical.find(oldWire); if (wireIt != wireToLogical.end() && wireIt->second == *logical) { wireToLogical.erase(wireIt); } } } size_t pos = 0; while (pos < json.size()) { size_t keyStart = json.find('"', pos); if (keyStart == std::string::npos) break; size_t keyEnd = json.find('"', keyStart + 1); if (keyEnd == std::string::npos) break; std::string key = json.substr(keyStart + 1, keyEnd - keyStart - 1); size_t colon = json.find(':', keyEnd); if (colon == std::string::npos) break; size_t valStart = colon + 1; while (valStart < json.size() && (json[valStart] == ' ' || json[valStart] == '\t' || json[valStart] == '\r' || json[valStart] == '\n' || json[valStart] == '"')) ++valStart; size_t valEnd = json.find_first_of(",}\"\r\n", valStart); if (valEnd == std::string::npos) valEnd = json.size(); std::string valStr = json.substr(valStart, valEnd - valStart); uint16_t wire = 0; try { if (valStr.size() > 2 && (valStr[0] == '0' && (valStr[1] == 'x' || valStr[1] == 'X'))) { wire = static_cast(std::stoul(valStr, nullptr, 16)); } else { wire = static_cast(std::stoul(valStr)); } } catch (...) { pos = valEnd + 1; continue; } auto logical = resolveLogicalOpcodeIndex(key); if (logical) { auto oldLogicalIt = logicalToWire.find(*logical); if (oldLogicalIt != logicalToWire.end()) { const uint16_t oldWire = oldLogicalIt->second; auto oldWireIt = wireToLogical.find(oldWire); if (oldWireIt != wireToLogical.end() && oldWireIt->second == *logical) { wireToLogical.erase(oldWireIt); } } auto oldWireIt = wireToLogical.find(wire); if (oldWireIt != wireToLogical.end() && oldWireIt->second != *logical) { logicalToWire.erase(oldWireIt->second); wireToLogical.erase(oldWireIt); } logicalToWire[*logical] = wire; wireToLogical[wire] = *logical; } pos = valEnd + 1; } loadingStack.erase(canonicalKey); return ok; } std::optional OpcodeTable::nameToLogical(const std::string& name) { const std::string_view canonical = canonicalOpcodeName(name); for (size_t i = 0; i < kOpcodeNameCount; ++i) { if (canonical == kOpcodeNames[i].name) return kOpcodeNames[i].op; } return std::nullopt; } const char* OpcodeTable::logicalToName(LogicalOpcode op) { uint16_t val = static_cast(op); for (size_t i = 0; i < kOpcodeNameCount; ++i) { if (static_cast(kOpcodeNames[i].op) == val) return kOpcodeNames[i].name; } return "UNKNOWN"; } bool OpcodeTable::loadFromJson(const std::string& path) { // Start fresh — resolved JSON inheritance is the single source of truth for opcode mappings. logicalToWire_.clear(); wireToLogical_.clear(); std::unordered_set loadingStack; if (!loadOpcodeJsonRecursive(std::filesystem::path(path), logicalToWire_, wireToLogical_, loadingStack) || logicalToWire_.empty()) { LOG_WARNING("OpcodeTable: no opcodes loaded from ", path); return false; } LOG_INFO("OpcodeTable: loaded ", logicalToWire_.size(), " opcodes from ", path); return true; } uint16_t OpcodeTable::toWire(LogicalOpcode op) const { auto it = logicalToWire_.find(static_cast(op)); return (it != logicalToWire_.end()) ? it->second : 0xFFFF; } std::optional OpcodeTable::fromWire(uint16_t wireValue) const { auto it = wireToLogical_.find(wireValue); if (it != wireToLogical_.end()) { return static_cast(it->second); } return std::nullopt; } bool OpcodeTable::hasOpcode(LogicalOpcode op) const { return logicalToWire_.count(static_cast(op)) > 0; } } // namespace game } // namespace wowee