2021-12-07 21:47:41 +00:00
|
|
|
#include "content/views/view_data_inspector.hpp"
|
2020-11-20 23:12:58 +00:00
|
|
|
|
2022-02-15 20:50:27 +00:00
|
|
|
#include <hex/ui/imgui_imhex_extensions.h>
|
|
|
|
|
2021-01-13 16:28:27 +00:00
|
|
|
#include <hex/providers/provider.hpp>
|
2020-11-20 23:12:58 +00:00
|
|
|
|
|
|
|
#include <cstring>
|
|
|
|
|
2022-10-01 21:16:55 +00:00
|
|
|
#include <hex/helpers/logger.hpp>
|
2022-10-02 12:18:40 +00:00
|
|
|
#include <hex/helpers/file.hpp>
|
|
|
|
|
2022-10-01 21:16:55 +00:00
|
|
|
#include <pl/pattern_language.hpp>
|
|
|
|
#include <pl/core/evaluator.hpp>
|
|
|
|
#include <pl/patterns/pattern.hpp>
|
|
|
|
|
2021-12-07 21:47:41 +00:00
|
|
|
namespace hex::plugin::builtin {
|
2020-11-20 23:12:58 +00:00
|
|
|
|
2021-01-13 00:24:27 +00:00
|
|
|
using NumberDisplayStyle = ContentRegistry::DataInspector::NumberDisplayStyle;
|
|
|
|
|
2021-12-07 21:47:41 +00:00
|
|
|
ViewDataInspector::ViewDataInspector() : View("hex.builtin.view.data_inspector.name") {
|
2021-03-27 10:36:36 +00:00
|
|
|
EventManager::subscribe<EventRegionSelected>(this, [this](Region region) {
|
2021-09-21 00:29:54 +00:00
|
|
|
auto provider = ImHexApi::Provider::get();
|
2020-12-27 14:39:06 +00:00
|
|
|
|
2021-09-21 00:29:54 +00:00
|
|
|
if (!ImHexApi::Provider::isValid() || region.address == (size_t)-1) {
|
2020-12-21 10:23:57 +00:00
|
|
|
this->m_validBytes = 0;
|
2021-09-20 21:40:36 +00:00
|
|
|
} else {
|
2022-09-26 09:53:29 +00:00
|
|
|
this->m_validBytes = u64(provider->getActualSize() - region.address);
|
2021-09-20 21:40:36 +00:00
|
|
|
this->m_startAddress = region.address;
|
2020-12-21 10:23:57 +00:00
|
|
|
}
|
|
|
|
|
2020-11-20 23:12:58 +00:00
|
|
|
this->m_shouldInvalidate = true;
|
|
|
|
});
|
2021-01-13 00:24:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ViewDataInspector::~ViewDataInspector() {
|
2021-03-27 10:36:36 +00:00
|
|
|
EventManager::unsubscribe<EventRegionSelected>(this);
|
2021-01-13 00:24:27 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ViewDataInspector::drawContent() {
|
|
|
|
if (this->m_shouldInvalidate) {
|
|
|
|
this->m_shouldInvalidate = false;
|
|
|
|
this->m_cachedData.clear();
|
2022-10-01 21:16:55 +00:00
|
|
|
|
2021-09-21 00:29:54 +00:00
|
|
|
auto provider = ImHexApi::Provider::get();
|
2022-10-01 21:16:55 +00:00
|
|
|
|
|
|
|
// Decode bytes using registered inspectors
|
2021-01-13 00:24:27 +00:00
|
|
|
for (auto &entry : ContentRegistry::DataInspector::getEntries()) {
|
|
|
|
if (this->m_validBytes < entry.requiredSize)
|
|
|
|
continue;
|
|
|
|
|
2022-08-01 11:20:20 +00:00
|
|
|
std::vector<u8> buffer(this->m_validBytes > entry.maxSize ? entry.maxSize : this->m_validBytes);
|
2021-01-13 00:24:27 +00:00
|
|
|
provider->read(this->m_startAddress, buffer.data(), buffer.size());
|
2021-03-02 21:09:38 +00:00
|
|
|
|
2022-05-27 18:46:16 +00:00
|
|
|
if (this->m_invert) {
|
|
|
|
for (auto &byte : buffer)
|
|
|
|
byte ^= 0xFF;
|
|
|
|
}
|
|
|
|
|
2022-10-02 12:18:40 +00:00
|
|
|
this->m_cachedData.push_back({
|
|
|
|
entry.unlocalizedName,
|
|
|
|
entry.generatorFunction(buffer, this->m_endian, this->m_numberDisplayStyle),
|
|
|
|
entry.editingFunction,
|
|
|
|
false
|
|
|
|
});
|
2020-11-20 23:36:38 +00:00
|
|
|
}
|
2020-11-20 23:12:58 +00:00
|
|
|
|
2022-10-02 12:18:40 +00:00
|
|
|
|
2022-10-01 21:16:55 +00:00
|
|
|
// Decode bytes using custom inspectors defined using the pattern language
|
2022-10-02 12:18:40 +00:00
|
|
|
const std::map<std::string, pl::core::Token::Literal> inVariables = {
|
|
|
|
{ "numberDisplayStyle", u128(this->m_numberDisplayStyle) }
|
|
|
|
};
|
|
|
|
|
2022-10-01 21:16:55 +00:00
|
|
|
pl::PatternLanguage runtime;
|
|
|
|
ContentRegistry::PatternLanguage::configureRuntime(runtime, nullptr);
|
|
|
|
|
|
|
|
runtime.setDataSource([this, provider](u64 offset, u8 *buffer, size_t size) {
|
|
|
|
provider->read(offset, buffer, size);
|
|
|
|
|
|
|
|
if (this->m_invert) {
|
|
|
|
for (size_t i = 0; i < size; i++)
|
|
|
|
buffer[i] ^= 0xFF;
|
|
|
|
}
|
|
|
|
}, provider->getBaseAddress(), provider->getActualSize());
|
|
|
|
|
|
|
|
runtime.setDangerousFunctionCallHandler([]{ return false; });
|
|
|
|
runtime.setDefaultEndian(this->m_endian);
|
|
|
|
runtime.setStartAddress(this->m_startAddress);
|
|
|
|
|
2022-10-02 12:18:40 +00:00
|
|
|
for (const auto &folderPath : fs::getDefaultPaths(fs::ImHexPath::Inspectors)) {
|
|
|
|
for (const auto &filePath : std::fs::recursive_directory_iterator(folderPath)) {
|
|
|
|
if (!filePath.exists() || !filePath.is_regular_file() || filePath.path().extension() != ".hexpat")
|
|
|
|
continue;
|
|
|
|
|
|
|
|
fs::File file(filePath, fs::File::Mode::Read);
|
|
|
|
if (file.isValid()) {
|
|
|
|
auto inspectorCode = file.readString();
|
|
|
|
|
|
|
|
if (!inspectorCode.empty()) {
|
|
|
|
if (runtime.executeString(inspectorCode, {}, inVariables, true)) {
|
|
|
|
const auto &patterns = runtime.getAllPatterns();
|
|
|
|
|
|
|
|
for (const auto &pattern : patterns) {
|
|
|
|
if (pattern->isHidden())
|
|
|
|
continue;
|
|
|
|
|
|
|
|
this->m_cachedData.push_back({
|
|
|
|
pattern->getDisplayName(),
|
|
|
|
[value = pattern->getFormattedValue()]() {
|
|
|
|
ImGui::TextUnformatted(value.c_str());
|
|
|
|
return value;
|
|
|
|
},
|
|
|
|
std::nullopt,
|
|
|
|
false
|
|
|
|
});
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
const auto& error = runtime.getError();
|
|
|
|
|
|
|
|
log::error("Failed to execute inspectors.hexpat!");
|
|
|
|
if (error.has_value())
|
|
|
|
log::error("{}", error.value().what());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-10-02 08:28:37 +00:00
|
|
|
}
|
2022-10-01 21:16:55 +00:00
|
|
|
}
|
|
|
|
}
|
2020-11-20 23:12:58 +00:00
|
|
|
|
2021-12-07 21:47:41 +00:00
|
|
|
if (ImGui::Begin(View::toWindowName("hex.builtin.view.data_inspector.name").c_str(), &this->getWindowOpenState(), ImGuiWindowFlags_NoCollapse)) {
|
2021-09-21 00:29:54 +00:00
|
|
|
auto provider = ImHexApi::Provider::get();
|
2020-12-27 14:39:06 +00:00
|
|
|
|
2021-09-21 00:29:54 +00:00
|
|
|
if (ImHexApi::Provider::isValid() && provider->isReadable() && this->m_validBytes > 0) {
|
2022-01-24 19:53:17 +00:00
|
|
|
if (ImGui::BeginTable("##datainspector", 2, ImGuiTableFlags_ScrollY | ImGuiTableFlags_Borders | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_RowBg, ImVec2(0, ImGui::GetTextLineHeightWithSpacing() * (this->m_cachedData.size() + 1)))) {
|
2021-01-03 16:12:20 +00:00
|
|
|
ImGui::TableSetupScrollFreeze(0, 1);
|
2021-12-07 21:47:41 +00:00
|
|
|
ImGui::TableSetupColumn("hex.builtin.view.data_inspector.table.name"_lang);
|
|
|
|
ImGui::TableSetupColumn("hex.builtin.view.data_inspector.table.value"_lang);
|
2021-01-03 16:12:20 +00:00
|
|
|
|
|
|
|
ImGui::TableHeadersRow();
|
|
|
|
|
2021-03-02 21:09:38 +00:00
|
|
|
u32 i = 0;
|
2022-02-15 20:50:27 +00:00
|
|
|
for (auto &[unlocalizedName, displayFunction, editingFunction, editing] : this->m_cachedData) {
|
2021-03-02 21:09:38 +00:00
|
|
|
ImGui::PushID(i);
|
2021-01-03 16:12:20 +00:00
|
|
|
ImGui::TableNextRow();
|
|
|
|
ImGui::TableNextColumn();
|
2021-02-13 14:15:32 +00:00
|
|
|
ImGui::TextUnformatted(LangEntry(unlocalizedName));
|
2021-01-03 16:12:20 +00:00
|
|
|
ImGui::TableNextColumn();
|
2022-02-15 20:50:27 +00:00
|
|
|
|
|
|
|
if (!editing) {
|
|
|
|
const auto ©Value = displayFunction();
|
|
|
|
ImGui::SameLine();
|
|
|
|
|
|
|
|
if (ImGui::Selectable("##InspectorLine", false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowItemOverlap)) {
|
|
|
|
ImGui::SetClipboardText(copyValue.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ImGui::IsItemHovered() && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && editingFunction.has_value()) {
|
|
|
|
editing = true;
|
|
|
|
this->m_editingValue = copyValue;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else {
|
|
|
|
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
|
|
|
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x);
|
|
|
|
ImGui::SetKeyboardFocusHere();
|
2022-03-03 08:24:09 +00:00
|
|
|
if (ImGui::InputText("##InspectorLineEditing", this->m_editingValue, ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll)) {
|
2022-02-15 20:50:27 +00:00
|
|
|
auto bytes = (*editingFunction)(this->m_editingValue, this->m_endian);
|
|
|
|
|
|
|
|
provider->write(this->m_startAddress, bytes.data(), bytes.size());
|
|
|
|
this->m_editingValue.clear();
|
|
|
|
editing = false;
|
|
|
|
this->m_shouldInvalidate = true;
|
|
|
|
}
|
|
|
|
ImGui::PopStyleVar();
|
|
|
|
|
|
|
|
if (!ImGui::IsItemHovered() && ImGui::IsAnyMouseDown()) {
|
|
|
|
this->m_editingValue.clear();
|
|
|
|
editing = false;
|
|
|
|
}
|
2021-03-02 21:09:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
ImGui::PopID();
|
|
|
|
i++;
|
2020-11-20 23:12:58 +00:00
|
|
|
}
|
2021-01-03 16:12:20 +00:00
|
|
|
|
|
|
|
ImGui::EndTable();
|
|
|
|
}
|
|
|
|
|
2022-01-15 14:15:25 +00:00
|
|
|
ImGui::NewLine();
|
|
|
|
ImGui::Separator();
|
2021-01-03 16:12:20 +00:00
|
|
|
ImGui::NewLine();
|
|
|
|
|
2022-03-26 16:44:01 +00:00
|
|
|
{
|
|
|
|
int selection = [this] {
|
|
|
|
switch (this->m_endian) {
|
|
|
|
default:
|
|
|
|
case std::endian::little: return 0;
|
|
|
|
case std::endian::big: return 1;
|
|
|
|
}
|
|
|
|
}();
|
|
|
|
|
|
|
|
std::array options = { "hex.builtin.common.little"_lang, "hex.builtin.common.big"_lang };
|
|
|
|
|
|
|
|
if (ImGui::SliderInt("hex.builtin.common.endian"_lang, &selection, 0, options.size() - 1, options[selection], ImGuiSliderFlags_NoInput)) {
|
|
|
|
this->m_shouldInvalidate = true;
|
|
|
|
|
|
|
|
switch (selection) {
|
|
|
|
default:
|
|
|
|
case 0: this->m_endian = std::endian::little; break;
|
|
|
|
case 1: this->m_endian = std::endian::big; break;
|
|
|
|
}
|
|
|
|
}
|
2020-11-20 23:12:58 +00:00
|
|
|
}
|
2020-11-22 14:32:37 +00:00
|
|
|
|
2022-03-26 16:44:01 +00:00
|
|
|
{
|
|
|
|
int selection = [this] {
|
|
|
|
switch (this->m_numberDisplayStyle) {
|
|
|
|
default:
|
|
|
|
case NumberDisplayStyle::Decimal: return 0;
|
|
|
|
case NumberDisplayStyle::Hexadecimal: return 1;
|
|
|
|
case NumberDisplayStyle::Octal: return 2;
|
|
|
|
}
|
|
|
|
}();
|
|
|
|
std::array options = { "hex.builtin.common.decimal"_lang, "hex.builtin.common.hexadecimal"_lang, "hex.builtin.common.octal"_lang };
|
|
|
|
|
|
|
|
if (ImGui::SliderInt("hex.builtin.common.number_format"_lang, &selection, 0, options.size() - 1, options[selection], ImGuiSliderFlags_NoInput)) {
|
|
|
|
this->m_shouldInvalidate = true;
|
|
|
|
|
|
|
|
switch (selection) {
|
|
|
|
default:
|
|
|
|
case 0: this->m_numberDisplayStyle = NumberDisplayStyle::Decimal; break;
|
|
|
|
case 1: this->m_numberDisplayStyle = NumberDisplayStyle::Hexadecimal; break;
|
|
|
|
case 2: this->m_numberDisplayStyle = NumberDisplayStyle::Octal; break;
|
|
|
|
}
|
|
|
|
}
|
2020-11-22 14:32:37 +00:00
|
|
|
}
|
2022-05-27 18:46:16 +00:00
|
|
|
|
|
|
|
{
|
|
|
|
int selection = this->m_invert ? 1 : 0;
|
|
|
|
std::array options = { "hex.builtin.common.no"_lang, "hex.builtin.common.yes"_lang };
|
|
|
|
|
|
|
|
if (ImGui::SliderInt("hex.builtin.view.data_inspector.invert"_lang, &selection, 0, options.size() - 1, options[selection], ImGuiSliderFlags_NoInput)) {
|
|
|
|
this->m_shouldInvalidate = true;
|
|
|
|
|
|
|
|
this->m_invert = selection == 1;
|
|
|
|
}
|
|
|
|
}
|
2021-09-20 21:40:36 +00:00
|
|
|
} else {
|
2022-02-01 21:09:44 +00:00
|
|
|
std::string text = "hex.builtin.view.data_inspector.no_data"_lang;
|
|
|
|
auto textSize = ImGui::CalcTextSize(text.c_str());
|
2021-09-20 21:40:36 +00:00
|
|
|
auto availableSpace = ImGui::GetContentRegionAvail();
|
|
|
|
|
|
|
|
ImGui::SetCursorPos((availableSpace - textSize) / 2.0F);
|
|
|
|
ImGui::TextUnformatted(text.c_str());
|
2020-11-20 23:12:58 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ImGui::End();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|