#include "content/views/view_pattern_editor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace hex::plugin::builtin { using namespace hex::literals; static const TextEditor::LanguageDefinition &PatternLanguage() { static bool initialized = false; static TextEditor::LanguageDefinition langDef; if (!initialized) { constexpr static std::array keywords = { "using", "struct", "union", "enum", "bitfield", "be", "le", "if", "else", "false", "true", "this", "parent", "addressof", "sizeof", "$", "while", "for", "fn", "return", "break", "continue", "namespace", "in", "out", "ref" }; for (auto &k : keywords) langDef.mKeywords.insert(k); constexpr static std::array builtInTypes = { "u8", "u16", "u24", "u32", "u48", "u64", "u96", "u128", "s8", "s16", "s24", "s32", "s48", "s64", "s96", "s128", "float", "double", "char", "char16", "bool", "padding", "str", "auto" }; for (const auto name : builtInTypes) { TextEditor::Identifier id; id.mDeclaration = ""; langDef.mIdentifiers.insert(std::make_pair(std::string(name), id)); } langDef.mTokenize = [](const char *inBegin, const char *inEnd, const char *&outBegin, const char *&outEnd, TextEditor::PaletteIndex &paletteIndex) -> bool { paletteIndex = TextEditor::PaletteIndex::Max; while (inBegin < inEnd && isascii(*inBegin) && isblank(*inBegin)) inBegin++; if (inBegin == inEnd) { outBegin = inEnd; outEnd = inEnd; paletteIndex = TextEditor::PaletteIndex::Default; } else if (TokenizeCStyleIdentifier(inBegin, inEnd, outBegin, outEnd)) { paletteIndex = TextEditor::PaletteIndex::Identifier; } else if (TokenizeCStyleNumber(inBegin, inEnd, outBegin, outEnd)) paletteIndex = TextEditor::PaletteIndex::Number; else if (TokenizeCStyleCharacterLiteral(inBegin, inEnd, outBegin, outEnd)) paletteIndex = TextEditor::PaletteIndex::CharLiteral; else if (TokenizeCStyleString(inBegin, inEnd, outBegin, outEnd)) paletteIndex = TextEditor::PaletteIndex::String; return paletteIndex != TextEditor::PaletteIndex::Max; }; langDef.mCommentStart = "/*"; langDef.mCommentEnd = "*/"; langDef.mSingleLineComment = "//"; langDef.mCaseSensitive = true; langDef.mAutoIndentation = true; langDef.mPreprocChar = '#'; langDef.mName = "Pattern Language"; initialized = true; } return langDef; } ViewPatternEditor::ViewPatternEditor() : View("hex.builtin.view.pattern_editor.name") { this->m_parserRuntime = std::make_unique(); ContentRegistry::PatternLanguage::configureRuntime(*this->m_parserRuntime, nullptr); this->m_textEditor.SetLanguageDefinition(PatternLanguage()); this->m_textEditor.SetShowWhitespaces(false); this->registerEvents(); this->registerMenuItems(); this->registerHandlers(); } ViewPatternEditor::~ViewPatternEditor() { EventManager::unsubscribe(this); EventManager::unsubscribe(this); EventManager::unsubscribe(this); EventManager::unsubscribe(this); EventManager::unsubscribe(this); } void ViewPatternEditor::drawContent() { if (ImGui::Begin(View::toWindowName("hex.builtin.view.pattern_editor.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_None | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { auto provider = ImHexApi::Provider::get(); if (ImHexApi::Provider::isValid() && provider->isAvailable()) { auto &extraData = ProviderExtraData::get(provider).patternLanguage; auto textEditorSize = ImGui::GetContentRegionAvail(); textEditorSize.y *= 3.75 / 5.0; textEditorSize.y -= ImGui::GetTextLineHeightWithSpacing(); this->m_textEditor.Render("hex.builtin.view.pattern_editor.name"_lang, textEditorSize, true); auto settingsSize = ImGui::GetContentRegionAvail(); settingsSize.y -= ImGui::GetTextLineHeightWithSpacing() * 2.5F; if (ImGui::BeginTabBar("##settings")) { if (ImGui::BeginTabItem("hex.builtin.view.pattern_editor.console"_lang)) { this->drawConsole(settingsSize, extraData.console); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("hex.builtin.view.pattern_editor.env_vars"_lang)) { this->drawEnvVars(settingsSize, extraData.envVarEntries); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("hex.builtin.view.pattern_editor.settings"_lang)) { this->drawVariableSettings(settingsSize, extraData.patternVariables); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("hex.builtin.view.pattern_editor.sections"_lang)) { this->drawSectionSelector(settingsSize, extraData.sections); ImGui::EndTabItem(); } ImGui::EndTabBar(); } ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 1); auto &runtime = ProviderExtraData::getCurrent().patternLanguage.runtime; if (runtime->isRunning()) { if (ImGui::IconButton(ICON_VS_DEBUG_STOP, ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarRed))) runtime->abort(); } else { if (ImGui::IconButton(ICON_VS_DEBUG_START, ImGui::GetCustomColorVec4(ImGuiCustomCol_ToolbarGreen))) { this->evaluatePattern(this->m_textEditor.GetText(), provider); } } ImGui::PopStyleVar(); ImGui::SameLine(); if (this->m_runningEvaluators > 0) ImGui::TextSpinner("hex.builtin.view.pattern_editor.evaluating"_lang); else { if (ImGui::Checkbox("hex.builtin.view.pattern_editor.auto"_lang, &this->m_runAutomatically)) { if (this->m_runAutomatically) this->m_hasUnevaluatedChanges = true; } ImGui::SameLine(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SameLine(); ImGui::TextFormatted("{} / {}", runtime->getCreatedPatternCount(), runtime->getMaximumPatternCount()); } if (this->m_textEditor.IsTextChanged()) { this->m_hasUnevaluatedChanges = true; ImHexApi::Provider::markDirty(); } if (this->m_hasUnevaluatedChanges && this->m_runningEvaluators == 0 && this->m_runningParsers == 0) { this->m_hasUnevaluatedChanges = false; TaskManager::createBackgroundTask("Pattern Parsing", [this, code = this->m_textEditor.GetText(), provider](auto &){ this->parsePattern(code, provider); if (this->m_runAutomatically) this->evaluatePattern(code, provider); }); } } if (this->m_dangerousFunctionCalled && !ImGui::IsPopupOpen(View::toWindowName("hex.builtin.view.pattern_editor.dangerous_function.name").c_str())) { ImGui::OpenPopup(View::toWindowName("hex.builtin.view.pattern_editor.dangerous_function.name").c_str()); this->m_dangerousFunctionCalled = false; } if (ImGui::BeginPopupModal(View::toWindowName("hex.builtin.view.pattern_editor.dangerous_function.name").c_str(), nullptr, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::NewLine(); ImGui::TextUnformatted("hex.builtin.view.pattern_editor.dangerous_function.desc"_lang); ImGui::NewLine(); View::confirmButtons( "hex.builtin.common.yes"_lang, "hex.builtin.common.no"_lang, [this] { this->m_dangerousFunctionsAllowed = DangerousFunctionPerms::Allow; ImGui::CloseCurrentPopup(); }, [this] { this->m_dangerousFunctionsAllowed = DangerousFunctionPerms::Deny; ImGui::CloseCurrentPopup(); }); ImGui::EndPopup(); } View::discardNavigationRequests(); } ImGui::End(); } void ViewPatternEditor::drawConsole(ImVec2 size, const std::vector> &console) { ImGui::PushStyleColor(ImGuiCol_ChildBg, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::Background)]); if (ImGui::BeginChild("##console", size, true, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_HorizontalScrollbar)) { ImGuiListClipper clipper; clipper.Begin(console.size()); while (clipper.Step()) for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { auto [level, message] = console[i]; std::replace_if(message.begin(), message.end(), [](char c) { return c == 0x00; }, ' '); switch (level) { using enum pl::core::LogConsole::Level; case Debug: ImGui::PushStyleColor(ImGuiCol_Text, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::Comment)]); break; case Info: ImGui::PushStyleColor(ImGuiCol_Text, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::Default)]); break; case Warning: ImGui::PushStyleColor(ImGuiCol_Text, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::Preprocessor)]); break; case Error: ImGui::PushStyleColor(ImGuiCol_Text, this->m_textEditor.GetPalette()[u32(TextEditor::PaletteIndex::ErrorMarker)]); break; default: continue; } if (ImGui::Selectable(hex::format("{}##ConsoleLine", message).c_str())) ImGui::SetClipboardText(message.c_str()); ImGui::PopStyleColor(); } } ImGui::EndChild(); ImGui::PopStyleColor(1); } void ViewPatternEditor::drawEnvVars(ImVec2 size, std::list &envVars) { static u32 envVarCounter = 1; if (ImGui::BeginChild("##env_vars", size, true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { int index = 0; if (ImGui::BeginTable("##env_vars_table", 4, ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_BordersInnerH)) { ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_WidthStretch, 0.1F); ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0.4F); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.38F); ImGui::TableSetupColumn("Remove", ImGuiTableColumnFlags_WidthStretch, 0.12F); for (auto iter = envVars.begin(); iter != envVars.end(); iter++) { ImGui::TableNextRow(); ImGui::TableNextColumn(); auto &[id, name, value, type] = *iter; ImGui::PushID(index++); ON_SCOPE_EXIT { ImGui::PopID(); }; ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); constexpr static const char *Types[] = { "I", "F", "S", "B" }; if (ImGui::BeginCombo("", Types[static_cast(type)])) { for (auto i = 0; i < IM_ARRAYSIZE(Types); i++) { if (ImGui::Selectable(Types[i])) type = static_cast(i); } ImGui::EndCombo(); } ImGui::PopItemWidth(); ImGui::TableNextColumn(); ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); ImGui::InputText("###name", name); ImGui::PopItemWidth(); ImGui::TableNextColumn(); ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x); switch (type) { using enum PlData::EnvVarType; case Integer: { i64 displayValue = hex::get_or(value, 0); ImGui::InputScalar("###value", ImGuiDataType_S64, &displayValue); value = i128(displayValue); break; } case Float: { auto displayValue = hex::get_or(value, 0.0); ImGui::InputDouble("###value", &displayValue); value = displayValue; break; } case Bool: { auto displayValue = hex::get_or(value, false); ImGui::Checkbox("###value", &displayValue); value = displayValue; break; } case String: { auto displayValue = hex::get_or(value, ""); ImGui::InputText("###value", displayValue); value = displayValue; break; } } ImGui::PopItemWidth(); ImGui::TableNextColumn(); if (ImGui::IconButton(ICON_VS_ADD, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { iter++; envVars.insert(iter, { envVarCounter++, "", i128(0), PlData::EnvVarType::Integer }); iter--; } ImGui::SameLine(); ImGui::BeginDisabled(envVars.size() <= 1); { if (ImGui::IconButton(ICON_VS_REMOVE, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { bool isFirst = iter == envVars.begin(); envVars.erase(iter); if (isFirst) iter = envVars.begin(); } } ImGui::EndDisabled(); } ImGui::EndTable(); } } ImGui::EndChild(); } void ViewPatternEditor::drawVariableSettings(ImVec2 size, std::map &patternVariables) { if (ImGui::BeginChild("##settings", size, true, ImGuiWindowFlags_AlwaysVerticalScrollbar)) { if (patternVariables.empty()) { ImGui::TextFormattedCentered("hex.builtin.view.pattern_editor.no_in_out_vars"_lang); } else { if (ImGui::BeginTable("##in_out_vars_table", 2, ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_BordersInnerH)) { ImGui::TableSetupColumn("Name", ImGuiTableColumnFlags_WidthStretch, 0.4F); ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthStretch, 0.6F); for (auto &[name, variable] : patternVariables) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextUnformatted(name.c_str()); ImGui::TableNextColumn(); if (variable.outVariable) { ImGui::TextUnformatted(pl::core::Token::literalToString(variable.value, true).c_str()); } else if (variable.inVariable) { const std::string label { "##" + name }; if (pl::core::Token::isSigned(variable.type)) { i64 value = hex::get_or(variable.value, 0); ImGui::InputScalar(label.c_str(), ImGuiDataType_S64, &value); variable.value = i128(value); } else if (pl::core::Token::isUnsigned(variable.type)) { u64 value = hex::get_or(variable.value, 0); ImGui::InputScalar(label.c_str(), ImGuiDataType_U64, &value); variable.value = u128(value); } else if (pl::core::Token::isFloatingPoint(variable.type)) { auto value = hex::get_or(variable.value, 0.0); ImGui::InputScalar(label.c_str(), ImGuiDataType_Double, &value); variable.value = value; } else if (variable.type == pl::core::Token::ValueType::Boolean) { auto value = hex::get_or(variable.value, false); ImGui::Checkbox(label.c_str(), &value); variable.value = value; } else if (variable.type == pl::core::Token::ValueType::Character) { char buffer[2]; ImGui::InputText(label.c_str(), buffer, 2); variable.value = buffer[0]; } } } ImGui::EndTable(); } } } ImGui::EndChild(); } void ViewPatternEditor::drawSectionSelector(ImVec2 size, std::map §ions) { if (ImGui::BeginTable("##sections_table", 3, ImGuiTableFlags_SizingStretchProp | ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, size)) { ImGui::TableSetupScrollFreeze(0, 1); ImGui::TableSetupColumn("hex.builtin.common.name"_lang, ImGuiTableColumnFlags_WidthStretch, 0.5F); ImGui::TableSetupColumn("hex.builtin.common.size"_lang, ImGuiTableColumnFlags_WidthStretch, 0.5F); ImGui::TableSetupColumn("##button", ImGuiTableColumnFlags_WidthFixed, 20_scaled); ImGui::TableHeadersRow(); for (auto &[id, section] : sections) { ImGui::PushID(id); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextUnformatted(section.name.c_str()); ImGui::TableNextColumn(); ImGui::TextFormatted("{} | 0x{:02X}", hex::toByteString(section.data.size()), section.data.size()); ImGui::TableNextColumn(); if (ImGui::IconButton(ICON_VS_OPEN_PREVIEW, ImGui::GetStyleColorVec4(ImGuiCol_Text))) { auto dataProvider = std::make_unique(); dataProvider->resize(section.data.size()); dataProvider->writeRaw(0x00, section.data.data(), section.data.size()); dataProvider->setReadOnly(true); auto hexEditor = ui::HexEditor(); hexEditor.setBackgroundHighlightCallback([this, id](u64 address, const u8 *, size_t) -> std::optional { if (this->m_runningEvaluators != 0) return std::nullopt; if (!ImHexApi::Provider::isValid()) return std::nullopt; std::optional color; for (const auto &pattern : ProviderExtraData::getCurrent().patternLanguage.runtime->getPatternsAtAddress(address, id)) { if (pattern->isHidden()) continue; if (color.has_value()) color = ImAlphaBlendColors(*color, pattern->getColor()); else color = pattern->getColor(); } return color; }); auto patternProvider = ImHexApi::Provider::get(); this->m_sectionWindowDrawer[patternProvider] = [id, patternProvider, dataProvider = std::move(dataProvider), hexEditor, patternDrawer = ui::PatternDrawer()] mutable { hexEditor.setProvider(dataProvider.get()); hexEditor.draw(480_scaled); patternDrawer.draw(ProviderExtraData::get(patternProvider).patternLanguage.runtime->getAllPatterns(id), 150_scaled); }; } ImGui::PopID(); } ImGui::EndTable(); } } void ViewPatternEditor::drawAlwaysVisible() { auto provider = ImHexApi::Provider::get(); ImGui::SetNextWindowPos(ImGui::GetMainViewport()->GetCenter(), ImGuiCond_Appearing, ImVec2(0.5F, 0.5F)); if (ImGui::BeginPopupModal("hex.builtin.view.pattern_editor.accept_pattern"_lang, &this->m_acceptPatternWindowOpen, ImGuiWindowFlags_AlwaysAutoResize)) { ImGui::TextFormattedWrapped("{}", static_cast("hex.builtin.view.pattern_editor.accept_pattern.desc"_lang)); std::vector entries; entries.resize(this->m_possiblePatternFiles.size()); for (u32 i = 0; i < entries.size(); i++) { entries[i] = hex::toUTF8String(this->m_possiblePatternFiles[i].filename()); } if (ImGui::BeginListBox("##patterns_accept", ImVec2(-FLT_MIN, 0))) { u32 index = 0; for (auto &path : this->m_possiblePatternFiles) { if (ImGui::Selectable(hex::toUTF8String(path.filename()).c_str(), index == this->m_selectedPatternFile)) this->m_selectedPatternFile = index; index++; } ImGui::EndListBox(); } ImGui::NewLine(); ImGui::TextUnformatted("hex.builtin.view.pattern_editor.accept_pattern.question"_lang); confirmButtons( "hex.builtin.common.yes"_lang, "hex.builtin.common.no"_lang, [this, provider] { this->loadPatternFile(this->m_possiblePatternFiles[this->m_selectedPatternFile], provider); ImGui::CloseCurrentPopup(); }, [] { ImGui::CloseCurrentPopup(); }); if (ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_Escape))) ImGui::CloseCurrentPopup(); ImGui::EndPopup(); } auto open = this->m_sectionWindowDrawer.contains(provider); if (open) { ImGui::SetNextWindowSize(scaled(ImVec2(600, 700)), ImGuiCond_Appearing); if (ImGui::Begin("hex.builtin.view.pattern_editor.section_popup"_lang, &open, ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoDocking | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) { this->m_sectionWindowDrawer[provider](); } ImGui::End(); } if (!open && this->m_sectionWindowDrawer.contains(provider)) { ImHexApi::HexEditor::setSelection(Region::Invalid()); this->m_sectionWindowDrawer.erase(provider); } auto &extraData = ProviderExtraData::get(provider).patternLanguage; if (!this->m_lastEvaluationProcessed) { extraData.console = extraData.lastEvaluationLog; if (!this->m_lastEvaluationResult) { if (extraData.lastEvaluationError) { TextEditor::ErrorMarkers errorMarkers = { { extraData.lastEvaluationError->line, extraData.lastEvaluationError->message } }; this->m_textEditor.SetErrorMarkers(errorMarkers); } } else { for (auto &[name, variable] : extraData.patternVariables) { if (variable.outVariable && extraData.lastEvaluationOutVars.contains(name)) variable.value = extraData.lastEvaluationOutVars.at(name); } EventManager::post(); } this->m_lastEvaluationProcessed = true; extraData.executionDone = true; } } void ViewPatternEditor::drawPatternTooltip(pl::ptrn::Pattern *pattern) { ImGui::PushID(pattern); { ImGui::ColorButton(pattern->getVariableName().c_str(), ImColor(pattern->getColor())); ImGui::SameLine(0, 10); ImGui::TextFormattedColored(ImColor(0xFF9BC64D), "{} ", pattern->getFormattedName()); ImGui::SameLine(0, 5); ImGui::TextFormatted("{}", pattern->getDisplayName()); ImGui::SameLine(); ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical); ImGui::SameLine(); ImGui::TextFormatted("{} ", hex::limitStringLength(pattern->getFormattedValue(), 64)); if (ImGui::GetIO().KeyShift) { ImGui::Indent(); if (ImGui::BeginTable("##extra_info", 2, ImGuiTableFlags_RowBg | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_NoClip)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextFormatted("{} ", "hex.builtin.common.type"_lang); ImGui::TableNextColumn(); ImGui::TextFormatted(" {}", pattern->getTypeName()); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextFormatted("{} ", "hex.builtin.common.address"_lang); ImGui::TableNextColumn(); ImGui::TextFormatted(" 0x{:08X}", pattern->getOffset()); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextFormatted("{} ", "hex.builtin.common.size"_lang); ImGui::TableNextColumn(); ImGui::TextFormatted(" {}", hex::toByteString(pattern->getSize())); ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextFormatted("{} ", "hex.builtin.common.endian"_lang); ImGui::TableNextColumn(); ImGui::TextFormatted(" {}", pattern->getEndian() == std::endian::little ? "hex.builtin.common.little"_lang : "hex.builtin.common.big"_lang); if (const auto &comment = pattern->getComment(); !comment.empty()) { ImGui::TableNextRow(); ImGui::TableNextColumn(); ImGui::TextFormatted("{} ", "hex.builtin.common.comment"_lang); ImGui::TableNextColumn(); ImGui::TextWrapped(" \"%s\"", comment.c_str()); } ImGui::EndTable(); } ImGui::Unindent(); } } ImGui::PopID(); } void ViewPatternEditor::loadPatternFile(const std::fs::path &path, prv::Provider *provider) { fs::File file(path, fs::File::Mode::Read); if (file.isValid()) { auto code = file.readString(); this->evaluatePattern(code, provider); this->m_textEditor.SetText(code); TaskManager::createBackgroundTask("Parse pattern", [this, code, provider](auto&) { this->parsePattern(code, provider); }); } } void ViewPatternEditor::parsePattern(const std::string &code, prv::Provider *provider) { this->m_runningParsers++; ContentRegistry::PatternLanguage::configureRuntime(*this->m_parserRuntime, nullptr); auto ast = this->m_parserRuntime->parseString(code); auto &patternLanguage = ProviderExtraData::get(provider).patternLanguage; patternLanguage.patternVariables.clear(); if (ast) { for (auto &node : *ast) { if (auto variableDecl = dynamic_cast(node.get())) { auto type = dynamic_cast(variableDecl->getType().get()); if (type == nullptr) continue; auto builtinType = dynamic_cast(type->getType().get()); if (builtinType == nullptr) continue; PlData::PatternVariable variable = { .inVariable = variableDecl->isInVariable(), .outVariable = variableDecl->isOutVariable(), .type = builtinType->getType(), .value = { } }; if (variable.inVariable || variable.outVariable) { if (!patternLanguage.patternVariables.contains(variableDecl->getName())) patternLanguage.patternVariables[variableDecl->getName()] = variable; } } } } this->m_runningParsers--; } void ViewPatternEditor::evaluatePattern(const std::string &code, prv::Provider *provider) { auto &patternLanguage = ProviderExtraData::get(provider).patternLanguage; this->m_runningEvaluators++; patternLanguage.executionDone = false; this->m_textEditor.SetErrorMarkers({}); patternLanguage.console.clear(); ContentRegistry::PatternLanguage::configureRuntime(*patternLanguage.runtime, provider); EventManager::post(); TaskManager::createTask("hex.builtin.view.pattern_editor.evaluating", TaskManager::NoProgress, [this, &patternLanguage, code](auto &task) { std::scoped_lock lock(patternLanguage.runtimeMutex); auto &runtime = patternLanguage.runtime; task.setInterruptCallback([&runtime] { runtime->abort(); }); std::map envVars; for (const auto &[id, name, value, type] : patternLanguage.envVarEntries) envVars.insert({ name, value }); std::map inVariables; for (auto &[name, variable] : patternLanguage.patternVariables) { if (variable.inVariable) inVariables[name] = variable.value; } runtime->setDangerousFunctionCallHandler([this]{ this->m_dangerousFunctionCalled = true; while (this->m_dangerousFunctionsAllowed == DangerousFunctionPerms::Ask) { std::this_thread::yield(); } return this->m_dangerousFunctionsAllowed == DangerousFunctionPerms::Allow; }); ON_SCOPE_EXIT { patternLanguage.lastEvaluationLog = runtime->getConsoleLog(); patternLanguage.lastEvaluationOutVars = runtime->getOutVariables(); patternLanguage.sections = runtime->getSections(); this->m_runningEvaluators--; this->m_lastEvaluationProcessed = false; patternLanguage.lastEvaluationLog.emplace_back( pl::core::LogConsole::Level::Info, hex::format("Evaluation took {}", runtime->getLastRunningTime()) ); }; this->m_lastEvaluationResult = runtime->executeString(code, envVars, inVariables); if (!this->m_lastEvaluationResult) { patternLanguage.lastEvaluationError = runtime->getError(); } }); } void ViewPatternEditor::registerEvents() { EventManager::subscribe(this, [this](const std::string &code) { this->m_textEditor.SetText(code); }); EventManager::subscribe(this, [this] { { auto syncPatternSource = ContentRegistry::Settings::getSetting("hex.builtin.setting.general", "hex.builtin.setting.general.sync_pattern_source"); if (syncPatternSource.is_number()) this->m_syncPatternSourceCode = static_cast(syncPatternSource); } { auto autoLoadPatterns = ContentRegistry::Settings::getSetting("hex.builtin.setting.general", "hex.builtin.setting.general.auto_load_patterns"); if (autoLoadPatterns.is_number()) this->m_autoLoadPatterns = static_cast(autoLoadPatterns); } }); EventManager::subscribe(this, [this](prv::Provider *provider) { auto &patternLanguageData = ProviderExtraData::get(provider).patternLanguage; patternLanguageData.runtime = std::make_unique(); ContentRegistry::PatternLanguage::configureRuntime(*patternLanguageData.runtime, provider); TaskManager::createBackgroundTask("Analyzing file content", [this, provider, &data = patternLanguageData](auto &) { if (!this->m_autoLoadPatterns) return; // Copy over current pattern source code to the new provider if (!this->m_syncPatternSourceCode) { data.sourceCode = this->m_textEditor.GetText(); } std::scoped_lock lock(data.runtimeMutex); auto &runtime = data.runtime; auto mimeType = magic::getMIMEType(provider); bool foundCorrectType = false; runtime->addPragma("MIME", [&mimeType, &foundCorrectType](pl::PatternLanguage &runtime, const std::string &value) { hex::unused(runtime); if (value == mimeType) { foundCorrectType = true; return true; } return !std::all_of(value.begin(), value.end(), isspace) && !value.ends_with('\n') && !value.ends_with('\r'); }); this->m_possiblePatternFiles.clear(); std::error_code errorCode; for (const auto &dir : fs::getDefaultPaths(fs::ImHexPath::Patterns)) { for (auto &entry : std::fs::recursive_directory_iterator(dir, errorCode)) { foundCorrectType = false; if (!entry.is_regular_file()) continue; fs::File file(entry.path(), fs::File::Mode::Read); if (!file.isValid()) continue; runtime->getInternals().preprocessor->preprocess(*runtime, file.readString()); if (foundCorrectType) this->m_possiblePatternFiles.push_back(entry.path()); runtime->reset(); } } runtime->addPragma("MIME", [](pl::PatternLanguage&, const std::string &value) { return !value.empty(); }); if (!this->m_possiblePatternFiles.empty()) { this->m_selectedPatternFile = 0; EventManager::post("hex.builtin.view.pattern_editor.accept_pattern"_lang); this->m_acceptPatternWindowOpen = true; } }); patternLanguageData.envVarEntries.push_back({ 0, "", 0, PlData::EnvVarType::Integer }); }); EventManager::subscribe(this, [this](prv::Provider *oldProvider, prv::Provider *newProvider) { if (!this->m_syncPatternSourceCode) { if (oldProvider != nullptr) ProviderExtraData::get(oldProvider).patternLanguage.sourceCode = this->m_textEditor.GetText(); if (newProvider != nullptr) this->m_textEditor.SetText(ProviderExtraData::get(newProvider).patternLanguage.sourceCode); else this->m_textEditor.SetText(""); auto lines = this->m_textEditor.GetTextLines(); lines.pop_back(); this->m_textEditor.SetTextLines(lines); } }); } static void createNestedMenu(const std::vector &menus, const std::function &function) { if (menus.empty()) return; if (menus.size() == 1) { if (ImGui::MenuItem(menus.front().c_str())) function(); } else { if (ImGui::BeginMenu(menus.front().c_str())) { createNestedMenu({ menus.begin() + 1, menus.end() }, function); ImGui::EndMenu(); } } } void ViewPatternEditor::registerMenuItems() { ContentRegistry::Interface::addMenuItem("hex.builtin.menu.file", 2000, [&, this] { bool providerValid = ImHexApi::Provider::isValid(); auto provider = ImHexApi::Provider::get(); if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.file.load_pattern"_lang, nullptr, false, providerValid)) { std::vector paths; for (const auto &imhexPath : fs::getDefaultPaths(fs::ImHexPath::Patterns)) { if (!fs::exists(imhexPath)) continue; std::error_code error; for (auto &entry : std::fs::recursive_directory_iterator(imhexPath, error)) { if (entry.is_regular_file() && entry.path().extension() == ".hexpat") { paths.push_back(entry.path()); } } } View::showFileChooserPopup(paths, { {"Pattern File", "hexpat"} }, [this, provider](const std::fs::path &path) { this->loadPatternFile(path, provider); }); } if (ImGui::MenuItem("hex.builtin.view.pattern_editor.menu.file.save_pattern"_lang, nullptr, false, providerValid)) { fs::openFileBrowser(fs::DialogMode::Save, { {"Pattern", "hexpat"} }, [this](const auto &path) { fs::File file(path, fs::File::Mode::Create); file.write(this->m_textEditor.GetText()); }); } }); // Place Pattern Type... ContentRegistry::Interface::addMenuItem("hex.builtin.menu.edit", 3000, [this] { bool available = ImHexApi::Provider::isValid() && ImHexApi::HexEditor::isSelectionValid() && this->m_runningParsers == 0; auto selection = ImHexApi::HexEditor::getSelection(); const auto &types = this->m_parserRuntime->getInternals().parser->getTypes(); const auto appendEditorText = [this](const std::string &text){ this->m_textEditor.SetCursorPosition(TextEditor::Coordinates { this->m_textEditor.GetTotalLines(), 0 }); this->m_textEditor.InsertText(hex::format("\n{0}", text)); this->m_hasUnevaluatedChanges = true; }; const auto appendVariable = [&](const std::string &type) { appendEditorText(hex::format("{0} {0}_at_0x{1:02X} @ 0x{1:02X};", type, selection->getStartAddress())); }; const auto appendArray = [&](const std::string &type, size_t size) { appendEditorText(hex::format("{0} {0}_array_at_0x{1:02X}[0x{2:02X}] @ 0x{1:02X};", type, selection->getStartAddress(), (selection->getSize() + (size - 1)) / size)); }; if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern"_lang, available)) { if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin"_lang)) { constexpr static std::array, 21> Types = {{ { "u8", 1 }, { "u16", 2 }, { "u24", 3 }, { "u32", 4 }, { "u48", 6 }, { "u64", 8 }, { "u96", 12 }, { "u128", 16 }, { "s8", 1 }, { "s16", 2 }, { "s24", 3 }, { "s32", 4 }, { "s48", 6 }, { "s64", 8 }, { "s96", 12 }, { "s128", 16 }, { "float", 4 }, { "double", 8 }, { "bool", 1 }, { "char", 1 }, { "char16", 2 } }}; if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.single"_lang)) { for (const auto &[type, size] : Types) if (ImGui::MenuItem(type)) appendVariable(type); ImGui::EndMenu(); } if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.builtin.array"_lang)) { for (const auto &[type, size] : Types) if (ImGui::MenuItem(type)) appendArray(type, size); ImGui::EndMenu(); } ImGui::EndMenu(); } if (ImGui::BeginMenu("hex.builtin.view.pattern_editor.menu.edit.place_pattern.custom"_lang, !types.empty())) { for (const auto &[typeName, type] : types) { if (type->isTemplateType()) continue; createNestedMenu(hex::splitString(typeName, "::"), [&] { std::string variableName; for (char &c : hex::replaceStrings(typeName, "::", "_")) variableName += static_cast(std::tolower(c)); variableName += hex::format("_at_0x{:02X}", selection->getStartAddress()); appendEditorText(hex::format("{0} {1} @ 0x{2:02X};", typeName, variableName, selection->getStartAddress())); }); } ImGui::EndMenu(); } ImGui::EndMenu(); } }); } void ViewPatternEditor::registerHandlers() { ContentRegistry::FileHandler::add({ ".hexpat", ".pat" }, [](const std::fs::path &path) -> bool { fs::File file(path, fs::File::Mode::Read); if (file.isValid()) { EventManager::post(file.readString()); return true; } else { return false; } }); ImHexApi::HexEditor::addBackgroundHighlightingProvider([this](u64 address, const u8 *data, size_t size, bool) -> std::optional { hex::unused(data, size); if (this->m_runningEvaluators != 0) return std::nullopt; std::optional color; for (const auto &pattern : ProviderExtraData::getCurrent().patternLanguage.runtime->getPatternsAtAddress(address)) { if (pattern->isHidden()) continue; if (color.has_value()) color = ImAlphaBlendColors(*color, pattern->getColor()); else color = pattern->getColor(); } return color; }); ImHexApi::HexEditor::addTooltipProvider([this](u64 address, const u8 *data, size_t size) { hex::unused(data, size); auto patterns = ProviderExtraData::getCurrent().patternLanguage.runtime->getPatternsAtAddress(address); if (!patterns.empty() && !std::all_of(patterns.begin(), patterns.end(), [](const auto &pattern) { return pattern->isHidden(); })) { ImGui::BeginTooltip(); for (const auto &pattern : patterns) { if (pattern->isHidden()) continue; auto tooltipColor = (pattern->getColor() & 0x00FF'FFFF) | 0x7000'0000; ImGui::PushID(pattern); if (ImGui::BeginTable("##tooltips", 1, ImGuiTableFlags_RowBg | ImGuiTableFlags_NoClip)) { ImGui::TableNextRow(); ImGui::TableNextColumn(); this->drawPatternTooltip(pattern); ImGui::PushStyleColor(ImGuiCol_TableRowBg, tooltipColor); ImGui::PushStyleColor(ImGuiCol_TableRowBgAlt, tooltipColor); ImGui::EndTable(); ImGui::PopStyleColor(2); } ImGui::PopID(); } ImGui::EndTooltip(); } }); ProjectFile::registerPerProviderHandler({ .basePath = "pattern_source_code.hexpat", .required = false, .load = [this](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) { std::string sourceCode = tar.readString(basePath); if (!this->m_syncPatternSourceCode) ProviderExtraData::get(provider).patternLanguage.sourceCode = sourceCode; if (provider == ImHexApi::Provider::get()) this->m_textEditor.SetText(sourceCode); return true; }, .store = [this](prv::Provider *provider, const std::fs::path &basePath, Tar &tar) { std::string sourceCode; if (provider == ImHexApi::Provider::get()) ProviderExtraData::get(provider).patternLanguage.sourceCode = this->m_textEditor.GetText(); if (this->m_syncPatternSourceCode) sourceCode = this->m_textEditor.GetText(); else sourceCode = ProviderExtraData::get(provider).patternLanguage.sourceCode; tar.write(basePath, sourceCode); return true; } }); } }