mirror of https://github.com/WerWolv/ImHex.git
1047 lines
47 KiB
C++
1047 lines
47 KiB
C++
#include "content/views/view_pattern_editor.hpp"
|
|
|
|
#include <hex/api/content_registry.hpp>
|
|
|
|
#include <pl/patterns/pattern.hpp>
|
|
#include <pl/core/preprocessor.hpp>
|
|
#include <pl/core/parser.hpp>
|
|
#include <pl/core/ast/ast_node_variable_decl.hpp>
|
|
#include <pl/core/ast/ast_node_type_decl.hpp>
|
|
#include <pl/core/ast/ast_node_builtin_type.hpp>
|
|
|
|
#include <hex/helpers/fs.hpp>
|
|
#include <hex/helpers/utils.hpp>
|
|
#include <hex/helpers/file.hpp>
|
|
#include <hex/api/project_file_manager.hpp>
|
|
#include <hex/helpers/magic.hpp>
|
|
|
|
#include <content/helpers/provider_extra_data.hpp>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
#include <chrono>
|
|
|
|
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<pl::PatternLanguage>();
|
|
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<RequestSetPatternLanguageCode>(this);
|
|
EventManager::unsubscribe<EventFileLoaded>(this);
|
|
EventManager::unsubscribe<EventProviderDeleted>(this);
|
|
EventManager::unsubscribe<RequestChangeTheme>(this);
|
|
EventManager::unsubscribe<EventProviderChanged>(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<std::pair<pl::core::LogConsole::Level, std::string>> &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<PlData::EnvVar> &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<int>(type)])) {
|
|
for (auto i = 0; i < IM_ARRAYSIZE(Types); i++) {
|
|
if (ImGui::Selectable(Types[i]))
|
|
type = static_cast<PlData::EnvVarType>(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<i128>(value, 0);
|
|
ImGui::InputScalar("###value", ImGuiDataType_S64, &displayValue);
|
|
value = i128(displayValue);
|
|
break;
|
|
}
|
|
case Float:
|
|
{
|
|
auto displayValue = hex::get_or<double>(value, 0.0);
|
|
ImGui::InputDouble("###value", &displayValue);
|
|
value = displayValue;
|
|
break;
|
|
}
|
|
case Bool:
|
|
{
|
|
auto displayValue = hex::get_or<bool>(value, false);
|
|
ImGui::Checkbox("###value", &displayValue);
|
|
value = displayValue;
|
|
break;
|
|
}
|
|
case String:
|
|
{
|
|
auto displayValue = hex::get_or<std::string>(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<std::string, PlData::PatternVariable> &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<i128>(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<u128>(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<double>(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<bool>(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<u64, pl::api::Section> §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<MemoryFileProvider>();
|
|
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<color_t> {
|
|
if (this->m_runningEvaluators != 0)
|
|
return std::nullopt;
|
|
if (!ImHexApi::Provider::isValid())
|
|
return std::nullopt;
|
|
|
|
std::optional<ImColor> 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<const char *>("hex.builtin.view.pattern_editor.accept_pattern.desc"_lang));
|
|
|
|
std::vector<std::string> 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<EventHighlightingChanged>();
|
|
}
|
|
|
|
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<pl::core::ast::ASTNodeVariableDecl *>(node.get())) {
|
|
auto type = dynamic_cast<pl::core::ast::ASTNodeTypeDecl *>(variableDecl->getType().get());
|
|
if (type == nullptr) continue;
|
|
|
|
auto builtinType = dynamic_cast<pl::core::ast::ASTNodeBuiltinType *>(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<EventHighlightingChanged>();
|
|
|
|
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<std::string, pl::core::Token::Literal> envVars;
|
|
for (const auto &[id, name, value, type] : patternLanguage.envVarEntries)
|
|
envVars.insert({ name, value });
|
|
|
|
std::map<std::string, pl::core::Token::Literal> 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<RequestSetPatternLanguageCode>(this, [this](const std::string &code) {
|
|
this->m_textEditor.SetText(code);
|
|
});
|
|
|
|
EventManager::subscribe<EventSettingsChanged>(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<int>(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<int>(autoLoadPatterns);
|
|
}
|
|
});
|
|
|
|
EventManager::subscribe<EventProviderOpened>(this, [this](prv::Provider *provider) {
|
|
auto &patternLanguageData = ProviderExtraData::get(provider).patternLanguage;
|
|
patternLanguageData.runtime = std::make_unique<pl::PatternLanguage>();
|
|
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<RequestOpenPopup>("hex.builtin.view.pattern_editor.accept_pattern"_lang);
|
|
this->m_acceptPatternWindowOpen = true;
|
|
}
|
|
});
|
|
|
|
patternLanguageData.envVarEntries.push_back({ 0, "", 0, PlData::EnvVarType::Integer });
|
|
});
|
|
|
|
EventManager::subscribe<EventProviderChanged>(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<std::string> &menus, const std::function<void()> &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<std::fs::path> 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<std::pair<const char *, size_t>, 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<char>(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<RequestSetPatternLanguageCode>(file.readString());
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
});
|
|
|
|
ImHexApi::HexEditor::addBackgroundHighlightingProvider([this](u64 address, const u8 *data, size_t size, bool) -> std::optional<color_t> {
|
|
hex::unused(data, size);
|
|
|
|
if (this->m_runningEvaluators != 0)
|
|
return std::nullopt;
|
|
|
|
std::optional<ImColor> 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;
|
|
}
|
|
});
|
|
}
|
|
|
|
}
|