Implemented crude support for custom encodings via thingy files

Relevant issue: #26
This commit is contained in:
WerWolv 2021-02-14 01:11:55 +01:00
parent 424bba71f7
commit b4c2f7d371
9 changed files with 277 additions and 15 deletions

View file

@ -42,6 +42,7 @@ add_executable(imhex ${application_type}
source/helpers/project_file_handler.cpp
source/helpers/loader_script_handler.cpp
source/helpers/plugin_handler.cpp
source/helpers/encoding_file.cpp
source/providers/file_provider.cpp

View file

@ -50,6 +50,8 @@
#include <hex/views/view.hpp>
#include <string>
#ifdef _MSC_VER
#define _PRISizeT "I"
#define ImSnprintf _snprintf
@ -75,12 +77,19 @@ struct MemoryEditor
DataFormat_COUNT
};
struct DecodeData {
std::string data;
size_t advance;
ImColor color;
};
// Settings
bool ReadOnly; // = false // disable any editing.
int Cols; // = 16 // number of columns to display.
bool OptShowOptions; // = true // display options button/context menu. when disabled, options will be locked unless you provide your own UI for them.
bool OptShowHexII; // = false // display values in HexII representation instead of regular hexadecimal: hide null/zero bytes, ascii values as ".X".
bool OptShowAscii; // = true // display ASCII representation on the right side.
bool OptShowAdvancedDecoding; // = true // display advanced decoding data on the right side.
bool OptGreyOutZeroes; // = true // display null/zero bytes using the TextDisabled color.
bool OptUpperCaseHex; // = true // display hexadecimal values as "FF" instead of "ff".
int OptMidColsCount; // = 8 // set to 0 to disable extra spacing between every mid-cols.
@ -90,6 +99,7 @@ struct MemoryEditor
void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes.
bool (*HighlightFn)(const ImU8* data, size_t off, bool next);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting).
void (*HoverFn)(const ImU8 *data, size_t off);
DecodeData (*DecodeFn)(const ImU8 *data, size_t off);
// [Internal State]
bool ContentsWidthChanged;
@ -114,6 +124,7 @@ struct MemoryEditor
OptShowOptions = true;
OptShowHexII = false;
OptShowAscii = true;
OptShowAdvancedDecoding = true;
OptGreyOutZeroes = true;
OptUpperCaseHex = true;
OptMidColsCount = 8;
@ -123,6 +134,7 @@ struct MemoryEditor
WriteFn = NULL;
HighlightFn = NULL;
HoverFn = NULL;
DecodeFn = NULL;
// State/Internals
ContentsWidthChanged = false;
@ -155,6 +167,8 @@ struct MemoryEditor
float PosHexEnd;
float PosAsciiStart;
float PosAsciiEnd;
float PosDecodingStart;
float PosDecodingEnd;
float WindowWidth;
Sizes() { memset(this, 0, sizeof(*this)); }
@ -174,12 +188,27 @@ struct MemoryEditor
s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth;
s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols);
s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd;
if (OptShowAscii)
{
if (OptShowAscii && OptShowAdvancedDecoding) {
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1;
if (OptMidColsCount > 0)
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth;
s.PosDecodingStart = s.PosAsciiEnd + s.GlyphWidth * 1;
if (OptMidColsCount > 0)
s.PosDecodingStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.PosDecodingEnd = s.PosDecodingStart + Cols * s.GlyphWidth;
} else if (OptShowAscii) {
s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1;
if (OptMidColsCount > 0)
s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth;
} else if (OptShowAdvancedDecoding) {
s.PosDecodingStart = s.PosHexEnd + s.GlyphWidth * 1;
if (OptMidColsCount > 0)
s.PosDecodingStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols;
s.PosDecodingEnd = s.PosDecodingStart + Cols * s.GlyphWidth;
}
s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth;
}
@ -222,15 +251,6 @@ struct MemoryEditor
CalcSizes(s, mem_size, base_display_addr);
ImGuiStyle& style = ImGui::GetStyle();
if (mem_size == 0x00) {
constexpr const char *noDataString = "No data loaded!";
auto pos = ImGui::GetCursorScreenPos();
pos.x += (ImGui::GetWindowWidth() - (ImGui::CalcTextSize(noDataString).x)) / 2;
ImGui::GetWindowDrawList()->AddText(pos, 0xFFFFFFFF, noDataString);
return;
}
// We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window.
// This is used as a facility since our main click detection code doesn't assign an ActiveId so the click would normally be caught as a window-move.
const float height_separator = style.ItemSpacing.y;
@ -327,6 +347,8 @@ struct MemoryEditor
ImVec2 window_pos = ImGui::GetWindowPos();
if (OptShowAscii)
draw_list->AddLine(ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosAsciiStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
if (OptShowAdvancedDecoding)
draw_list->AddLine(ImVec2(window_pos.x + s.PosDecodingStart - s.GlyphWidth, window_pos.y), ImVec2(window_pos.x + s.PosDecodingStart - s.GlyphWidth, window_pos.y + 9999), ImGui::GetColorU32(ImGuiCol_Border));
const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text);
const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text;
@ -541,6 +563,86 @@ struct MemoryEditor
pos.x += s.GlyphWidth;
}
ImGui::PushID(-1);
ImGui::SameLine();
ImGui::Dummy(ImVec2(s.GlyphWidth, s.LineHeight));
ImGui::PopID();
}
if (OptShowAdvancedDecoding && DecodeFn) {
// Draw decoded bytes
ImGui::SameLine(s.PosDecodingStart);
ImVec2 pos = ImGui::GetCursorScreenPos();
addr = line_i * Cols;
ImGui::PushID(-1);
ImGui::SameLine();
ImGui::Dummy(ImVec2(s.GlyphWidth, s.LineHeight));
ImGui::PopID();
for (int n = 0; n < Cols && addr < mem_size;)
{
auto decodedData = DecodeFn(mem_data, addr);
auto displayData = decodedData.data;
auto decodedDataLength = displayData.length();
if (addr == DataEditingAddr)
{
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth * decodedDataLength, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg));
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth * decodedDataLength, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg));
}
draw_list->AddText(pos, decodedData.color, displayData.c_str(), displayData.c_str() + decodedDataLength);
// Draw highlight
bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax);
bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr, false));
bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr <= DataPreviewAddrEnd) || (addr >= DataPreviewAddrEnd && addr <= DataPreviewAddr);
if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview)
{
ImU32 color = HighlightColor;
if ((is_highlight_from_user_range + is_highlight_from_user_func + is_highlight_from_preview) > 1)
color = (ImAlphaBlendColors(HighlightColor, 0x60C08080) & 0x00FFFFFF) | 0x90000000;
draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth * decodedDataLength, pos.y + s.LineHeight), color);
}
ImGui::PushID(line_i * Cols + n);
ImGui::SameLine();
ImGui::Dummy(ImVec2(s.GlyphWidth * decodedDataLength, s.LineHeight));
ImGui::PopID();
if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0) && !ImGui::GetIO().KeyShift)
{
if (!ReadOnly && ImGui::IsMouseDoubleClicked(0)) {
DataEditingTakeFocus = true;
data_editing_addr_next = addr;
}
DataPreviewAddr = addr;
DataPreviewAddrEnd = addr;
}
if (ImGui::IsItemHovered() && ((ImGui::IsMouseClicked(0) && ImGui::GetIO().KeyShift) || ImGui::IsMouseDragging(0))) {
DataPreviewAddrEnd = addr;
}
pos.x += s.GlyphWidth * decodedDataLength;
if (addr <= 1) {
n++;
addr++;
} else {
n += decodedData.advance;
addr += decodedData.advance;
}
}
}
}
IM_ASSERT(clipper.Step() == false);
@ -585,6 +687,7 @@ struct MemoryEditor
ImGui::PopItemWidth();
ImGui::Checkbox("Show HexII", &OptShowHexII);
if (ImGui::Checkbox("Show Ascii", &OptShowAscii)) { ContentsWidthChanged = true; }
if (ImGui::Checkbox("Show Advanced Decoding", &OptShowAdvancedDecoding)) { ContentsWidthChanged = true; }
ImGui::Checkbox("Grey out zeroes", &OptGreyOutZeroes);
ImGui::Checkbox("Uppercase Hex", &OptUpperCaseHex);

View file

@ -0,0 +1,37 @@
#pragma once
#include <hex.hpp>
#include <map>
#include <vector>
namespace hex {
template<typename T>
struct SizeSorter {
bool operator() (const T& lhs, const T& rhs) const {
return lhs.size() < rhs.size();
}
};
class EncodingFile {
public:
enum class Type {
Thingy,
CSV
};
EncodingFile() = default;
EncodingFile(Type type, std::string_view path);
std::pair<std::string_view, size_t> getEncodingFor(const std::vector<u8> &buffer) const;
size_t getLongestSequence() const { return this->m_longestSequence; }
private:
void parseThingyFile(std::ifstream &content);
std::map<u32, std::map<std::vector<u8>, std::string>> m_mapping;
size_t m_longestSequence = 0;
};
}

View file

@ -2,6 +2,7 @@
#include <hex/helpers/utils.hpp>
#include <hex/views/view.hpp>
#include "helpers/encoding_file.hpp"
#include <imgui_memory_editor.h>
#include <ImGuiFileBrowser.h>
@ -54,6 +55,8 @@ namespace hex {
std::string m_loaderScriptScriptPath;
std::string m_loaderScriptFilePath;
hex::EncodingFile m_currEncodingFile;
void drawSearchPopup();
void drawGotoPopup();
void drawEditPopup();

View file

@ -130,6 +130,7 @@ namespace hex::plugin::builtin {
{ "hex.view.hexeditor.save_project", "Save Project" },
{ "hex.view.hexeditor.save_data", "Save Data" },
{ "hex.view.hexeditor.open_base64", "Open Base64 File" },
{ "hex.view.hexeditor.load_enconding_file", "Load custom encoding File" },
{ "hex.view.hexeditor.page", "Page %d / %d" },
{ "hex.view.hexeditor.save_as", "Save As" },
{ "hex.view.hexeditor.save_changes.title", "Save Changes" },
@ -146,6 +147,7 @@ namespace hex::plugin::builtin {
{ "hex.view.hexeditor.menu.file.save_as", "Save As..." },
{ "hex.view.hexeditor.menu.file.open_project", "Open Project..." },
{ "hex.view.hexeditor.menu.file.save_project", "Save Project..." },
{ "hex.view.hexeditor.menu.file.load_encoding_file", "Load custom encoding..." },
{ "hex.view.hexeditor.menu.file.import", "Import..." },
{ "hex.view.hexeditor.menu.file.import.base64", "Base64 File" },
{ "hex.view.hexeditor.base64.import_error", "File is not in a valid Base64 format!" },

View file

@ -180,6 +180,23 @@ namespace hex {
return bytes;
}
inline std::vector<u8> parseByteString(std::string_view string) {
auto byteString = std::string(string);
byteString.erase(std::remove(byteString.begin(), byteString.end(), ' '), byteString.end());
if ((byteString.length() % 2) != 0) return { };
std::vector<u8> result;
for (u32 i = 0; i < byteString.length(); i += 2) {
if (!std::isxdigit(byteString[i]) || !std::isxdigit(byteString[i + 1]))
return { };
result.push_back(std::strtoul(byteString.substr(i, 2).c_str(), nullptr, 16));
}
return result;
}
inline std::string toBinaryString(hex::integral auto number) {
if (number == 0) return "0";
@ -190,6 +207,23 @@ namespace hex {
return result;
}
inline void trimLeft(std::string &s) {
s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) {
return !std::isspace(ch);
}));
}
inline void trimRight(std::string &s) {
s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) {
return !std::isspace(ch);
}).base(), s.end());
}
inline void trim(std::string &s) {
trimLeft(s);
trimRight(s);
}
#define SCOPE_EXIT(func) ScopeExit TOKEN_CONCAT(scopeGuard, __COUNTER__)([&] { func })
class ScopeExit {
public:

View file

@ -30,8 +30,6 @@ namespace hex {
json[unlocalizedCategory.data()] = nlohmann::json::object();
if (!json[unlocalizedCategory.data()].contains(unlocalizedName.data()))
json[unlocalizedCategory.data()][unlocalizedName.data()] = defaultValue;
Settings::store();
}
void ContentRegistry::Settings::add(std::string_view unlocalizedCategory, std::string_view unlocalizedName, std::string_view defaultValue, const std::function<bool(std::string_view, nlohmann::json&)> &callback) {
@ -43,8 +41,6 @@ namespace hex {
json[unlocalizedCategory.data()] = nlohmann::json::object();
if (!json[unlocalizedCategory.data()].contains(unlocalizedName.data()))
json[unlocalizedCategory.data()][unlocalizedName.data()] = defaultValue;
Settings::store();
}
void ContentRegistry::Settings::write(std::string_view unlocalizedCategory, std::string_view unlocalizedName, s64 value) {

View file

@ -0,0 +1,53 @@
#include "helpers/encoding_file.hpp"
#include <hex/helpers/utils.hpp>
#include <fstream>
namespace hex {
EncodingFile::EncodingFile(Type type, std::string_view path) {
std::ifstream encodingFile(path.data());
switch (type) {
case Type::Thingy: parseThingyFile(encodingFile); break;
default: throw std::runtime_error("Invalid encoding file type");
}
}
std::pair<std::string_view, size_t> EncodingFile::getEncodingFor(const std::vector<u8> &buffer) const {
for (auto iter = this->m_mapping.rbegin(); iter != this->m_mapping.rend(); iter++) {
auto &[size, mapping] = *iter;
if (size > buffer.size()) continue;
auto key = std::vector<u8>(buffer.begin(), buffer.begin() + size);
if (mapping.contains(key))
return { mapping.at(key), size };
}
return { ".", 1 };
}
void EncodingFile::parseThingyFile(std::ifstream &content) {
for (std::string line; std::getline(content, line);) {
auto entry = hex::splitString(line, "=");
if (entry.size() != 2) return;
auto &from = entry[0];
auto &to = entry[1];
hex::trim(from);
hex::trim(to);
auto fromBytes = hex::parseByteString(from);
if (!this->m_mapping.contains(fromBytes.size()))
this->m_mapping.insert({ fromBytes.size(), { } });
this->m_mapping[fromBytes.size()].insert({ fromBytes, to });
this->m_longestSequence = std::max(this->m_longestSequence, fromBytes.size());
}
}
}

View file

@ -99,6 +99,30 @@ namespace hex {
ImGui::EndTooltip();
};
this->m_memoryEditor.DecodeFn = [](const ImU8 *data, size_t addr) -> MemoryEditor::DecodeData {
ViewHexEditor *_this = (ViewHexEditor *) data;
if (_this->m_currEncodingFile.getLongestSequence() == 0)
return { ".", 1, 0xFFFF8000 };
auto &provider = SharedData::currentProvider;
size_t size = std::min<size_t>(_this->m_currEncodingFile.getLongestSequence(), provider->getActualSize() - addr);
std::vector<u8> buffer(size);
provider->read(addr, buffer.data(), size);
auto [decoded, advance] = _this->m_currEncodingFile.getEncodingFor(buffer);
ImColor color;
if (decoded.length() == 1 && std::isalnum(decoded[0])) color = 0xFFFF8000;
else if (decoded.length() == 1 && advance == 1) color = 0xFF0000FF;
else if (decoded.length() > 1 && advance == 1) color = 0xFF00FFFF;
else if (advance > 1) color = 0xFFFFFFFF;
else color = 0xFFFF8000;
return { std::string(decoded), advance, color };
};
View::subscribeEvent(Events::FileDropped, [this](auto userData) {
auto filePath = std::any_cast<const char*>(userData);
@ -358,6 +382,12 @@ namespace hex {
ProjectFile::store();
}
if (ImGui::MenuItem("hex.view.hexeditor.menu.file.load_encoding_file"_lang)) {
View::openFileBrowser("hex.view.hexeditor.load_enconding_file"_lang, imgui_addons::ImGuiFileBrowser::DialogMode::OPEN, "*.*", [this](auto path) {
this->m_currEncodingFile = EncodingFile(EncodingFile::Type::Thingy, path);
});
}
ImGui::Separator();
if (ImGui::BeginMenu("hex.view.hexeditor.menu.file.import"_lang)) {
@ -515,6 +545,9 @@ namespace hex {
if (!provider->isAvailable()) {
View::showErrorPopup("hex.view.hexeditor.error.open"_lang);
delete provider;
provider = nullptr;
return;
}