720 lines
31 KiB
C
720 lines
31 KiB
C
#pragma once
|
|
// Ported to C from
|
|
// https://github.com/ocornut/imgui_club/blob/master/imgui_memory_editor/imgui_memory_editor.h
|
|
|
|
#include "imgui_memory_editor.h"
|
|
|
|
// FIXME: We should have a way to retrieve the text edit cursor position
|
|
// more easily in the API, this is rather tedious. This is such a ugly mess
|
|
// we may be better off not using InputText() at all here.
|
|
static int UserData_Callback(ImGuiInputTextCallbackData* data) {
|
|
UserData* user_data = (UserData*)data->UserData;
|
|
if (!ImGuiInputTextCallbackData_HasSelection(data)) user_data->CursorPos = data->CursorPos;
|
|
if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen) {
|
|
// When not editing a byte, always refresh its InputText content
|
|
// pulled from underlying memory data (this is a bit tricky, since
|
|
// InputText technically "owns" the master copy of the buffer we
|
|
// edit it in there)
|
|
ImGuiInputTextCallbackData_DeleteChars(data, 0, data->BufTextLen);
|
|
ImGuiInputTextCallbackData_InsertChars(data, 0, user_data->CurrentBufOverwrite, NULL);
|
|
data->SelectionStart = 0;
|
|
data->SelectionEnd = 2;
|
|
data->CursorPos = 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void MemoryEditor_Init(MemoryEditor* editor) {
|
|
// Settings
|
|
editor->Open = true;
|
|
editor->ReadOnly = false;
|
|
editor->Cols = 16;
|
|
editor->OptShowOptions = true;
|
|
editor->OptShowDataPreview = false;
|
|
editor->OptShowHexII = false;
|
|
editor->OptShowAscii = true;
|
|
editor->OptGreyOutZeroes = true;
|
|
editor->OptUpperCaseHex = true;
|
|
editor->OptMidColsCount = 8;
|
|
editor->OptAddrDigitsCount = 0;
|
|
editor->OptFooterExtraHeight = 0.0f;
|
|
editor->HighlightColor = IM_COL32(255, 255, 255, 50);
|
|
editor->ReadFn = NULL;
|
|
editor->WriteFn = NULL;
|
|
editor->HighlightFn = NULL;
|
|
|
|
// State/Internals
|
|
editor->ContentsWidthChanged = false;
|
|
editor->DataPreviewAddr = editor->DataEditingAddr = (size_t)-1;
|
|
editor->DataEditingTakeFocus = false;
|
|
memset(editor->DataInputBuf, 0, sizeof(editor->DataInputBuf));
|
|
memset(editor->AddrInputBuf, 0, sizeof(editor->AddrInputBuf));
|
|
editor->GotoAddr = (size_t)-1;
|
|
editor->HighlightMin = editor->HighlightMax = (size_t)-1;
|
|
editor->PreviewEndianess = 0;
|
|
editor->PreviewDataType = ImGuiDataType_S32;
|
|
}
|
|
|
|
void MemoryEditor_GotoAddrAndHighlight(MemoryEditor* editor, size_t addr_min, size_t addr_max) {
|
|
editor->GotoAddr = addr_min;
|
|
editor->HighlightMin = addr_min;
|
|
editor->HighlightMax = addr_max;
|
|
}
|
|
|
|
void MemoryEditor_CalcSizes(MemoryEditor* editor, Sizes* s, size_t mem_size,
|
|
size_t base_display_addr) {
|
|
ImGuiStyle* style = igGetStyle();
|
|
s->AddrDigitsCount = editor->OptAddrDigitsCount;
|
|
if (s->AddrDigitsCount == 0)
|
|
for (size_t n = base_display_addr + mem_size - 1; n > 0; n >>= 4) s->AddrDigitsCount++;
|
|
s->LineHeight = igGetTextLineHeight();
|
|
ImVec2 textSize;
|
|
igCalcTextSize(&textSize, "F", NULL, false, -1.0f);
|
|
s->GlyphWidth = textSize.x + 1; // We assume the font is mono-space
|
|
s->HexCellWidth =
|
|
(float)(int)(s->GlyphWidth * 2.5f); // "FF " we include trailing space in the
|
|
// width to easily catch clicks everywhere
|
|
s->SpacingBetweenMidCols =
|
|
(float)(int)(s->HexCellWidth *
|
|
0.25f); // Every OptMidColsCount columns we add a bit of extra spacing
|
|
s->PosHexStart = (s->AddrDigitsCount + 2) * s->GlyphWidth;
|
|
s->PosHexEnd = s->PosHexStart + (s->HexCellWidth * editor->Cols);
|
|
s->PosAsciiStart = s->PosAsciiEnd = s->PosHexEnd;
|
|
if (editor->OptShowAscii) {
|
|
s->PosAsciiStart = s->PosHexEnd + s->GlyphWidth * 1;
|
|
if (editor->OptMidColsCount > 0)
|
|
s->PosAsciiStart +=
|
|
(float)((editor->Cols + editor->OptMidColsCount - 1) / editor->OptMidColsCount) *
|
|
s->SpacingBetweenMidCols;
|
|
s->PosAsciiEnd = s->PosAsciiStart + editor->Cols * s->GlyphWidth;
|
|
}
|
|
s->WindowWidth =
|
|
s->PosAsciiEnd + style->ScrollbarSize + style->WindowPadding.x * 2 + s->GlyphWidth;
|
|
}
|
|
|
|
// Standalone Memory Editor window
|
|
void MemoryEditor_DrawWindow(MemoryEditor* editor, const char* title, void* mem_data,
|
|
size_t mem_size, size_t base_display_addr) {
|
|
Sizes s;
|
|
MemoryEditor_CalcSizes(editor, &s, mem_size, base_display_addr);
|
|
|
|
ImVec2 vec1, vec2;
|
|
vec1.x = s.WindowWidth;
|
|
vec1.y = s.WindowWidth * 0.60f;
|
|
igSetNextWindowSize(vec1, ImGuiCond_FirstUseEver);
|
|
vec1.x = 0.0f;
|
|
vec1.y = 0.0f;
|
|
vec2.x = s.WindowWidth;
|
|
vec2.y = FLT_MAX;
|
|
igSetNextWindowSizeConstraints(vec1, vec2, NULL, NULL);
|
|
|
|
editor->Open = true;
|
|
if (igBegin(title, &editor->Open, ImGuiWindowFlags_NoScrollbar)) {
|
|
if (igIsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) &&
|
|
igIsMouseReleased_Nil(ImGuiMouseButton_Right))
|
|
igOpenPopup_Str("context", 0);
|
|
MemoryEditor_DrawContents(editor, mem_data, mem_size, base_display_addr);
|
|
if (editor->ContentsWidthChanged) {
|
|
MemoryEditor_CalcSizes(editor, &s, mem_size, base_display_addr);
|
|
igGetWindowSize(&vec1);
|
|
vec1.x = s.WindowWidth;
|
|
igSetWindowSize_Vec2(vec1, 0);
|
|
}
|
|
}
|
|
igEnd();
|
|
}
|
|
|
|
// Memory Editor contents only
|
|
void MemoryEditor_DrawContents(MemoryEditor* editor, void* mem_data_void, size_t mem_size,
|
|
size_t base_display_addr) {
|
|
if (editor->Cols < 1) editor->Cols = 1;
|
|
|
|
ImU8* mem_data = (ImU8*)mem_data_void;
|
|
Sizes s;
|
|
MemoryEditor_CalcSizes(editor, &s, mem_size, base_display_addr);
|
|
ImGuiStyle* style = igGetStyle();
|
|
|
|
// 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;
|
|
float footer_height = editor->OptFooterExtraHeight;
|
|
if (editor->OptShowOptions)
|
|
footer_height += height_separator + igGetFrameHeightWithSpacing() * 1;
|
|
if (editor->OptShowDataPreview)
|
|
footer_height += height_separator + igGetFrameHeightWithSpacing() * 1 +
|
|
igGetTextLineHeightWithSpacing() * 3;
|
|
|
|
ImVec2 vec1, vec2;
|
|
vec1.x = 0;
|
|
vec1.y = -footer_height;
|
|
igBeginChild_Str("##scrolling", vec1, false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav);
|
|
ImDrawList* draw_list = igGetWindowDrawList();
|
|
|
|
vec1.y = 0;
|
|
igPushStyleVar_Vec2(ImGuiStyleVar_FramePadding, vec1);
|
|
igPushStyleVar_Vec2(ImGuiStyleVar_ItemSpacing, vec1);
|
|
|
|
// We are not really using the clipper API correctly here, because we rely on
|
|
// visible_start_addr/visible_end_addr for our scrolling function.
|
|
const int line_total_count = (int)((mem_size + editor->Cols - 1) / editor->Cols);
|
|
ImGuiListClipper clipper;
|
|
ImGuiListClipper_Begin(&clipper, line_total_count, s.LineHeight);
|
|
|
|
bool data_next = false;
|
|
|
|
if (editor->ReadOnly || editor->DataEditingAddr >= mem_size)
|
|
editor->DataEditingAddr = (size_t)-1;
|
|
if (editor->DataPreviewAddr >= mem_size) editor->DataPreviewAddr = (size_t)-1;
|
|
|
|
size_t preview_data_type_size =
|
|
editor->OptShowDataPreview ? MemoryEditor_DataTypeGetSize(editor->PreviewDataType) : 0;
|
|
|
|
size_t data_editing_addr_next = (size_t)-1;
|
|
if (editor->DataEditingAddr != (size_t)-1) {
|
|
// Move cursor but only apply on next frame so scrolling with be synchronized (because
|
|
// currently we can't change the scrolling while the window is being rendered)
|
|
if (igIsKeyPressed_Bool(igGetKeyIndex(ImGuiKey_UpArrow), true) &&
|
|
(ptrdiff_t)editor->DataEditingAddr >= (ptrdiff_t)editor->Cols) {
|
|
data_editing_addr_next = editor->DataEditingAddr - editor->Cols;
|
|
} else if (igIsKeyPressed_Bool(igGetKeyIndex(ImGuiKey_DownArrow), true) &&
|
|
(ptrdiff_t)editor->DataEditingAddr < (ptrdiff_t)mem_size - editor->Cols) {
|
|
data_editing_addr_next = editor->DataEditingAddr + editor->Cols;
|
|
} else if (igIsKeyPressed_Bool(igGetKeyIndex(ImGuiKey_LeftArrow), true) &&
|
|
(ptrdiff_t)editor->DataEditingAddr > (ptrdiff_t)0) {
|
|
data_editing_addr_next = editor->DataEditingAddr - 1;
|
|
} else if (igIsKeyPressed_Bool(igGetKeyIndex(ImGuiKey_RightArrow), true) &&
|
|
(ptrdiff_t)editor->DataEditingAddr < (ptrdiff_t)mem_size - 1) {
|
|
data_editing_addr_next = editor->DataEditingAddr + 1;
|
|
}
|
|
}
|
|
|
|
// Draw vertical separator
|
|
ImVec2 window_pos;
|
|
igGetWindowPos(&window_pos);
|
|
if (editor->OptShowAscii) {
|
|
vec1.x = window_pos.x + s.PosAsciiStart - s.GlyphWidth;
|
|
vec1.y = window_pos.y;
|
|
vec2.x = window_pos.x + s.PosAsciiStart - s.GlyphWidth;
|
|
vec2.y = window_pos.y + 9999;
|
|
ImDrawList_AddLine(draw_list, vec1, vec2, igGetColorU32_Col(ImGuiCol_Border, 1.0f), 1);
|
|
}
|
|
|
|
const ImU32 color_text = igGetColorU32_Col(ImGuiCol_Text, 1.0f);
|
|
const ImU32 color_disabled =
|
|
editor->OptGreyOutZeroes ? igGetColorU32_Col(ImGuiCol_TextDisabled, 1.0f) : color_text;
|
|
|
|
const char* format_address =
|
|
editor->OptUpperCaseHex ? "%0*" _PRISizeT "X: " : "%0*" _PRISizeT "x: ";
|
|
const char* format_data = editor->OptUpperCaseHex ? "%0*" _PRISizeT "X" : "%0*" _PRISizeT "x";
|
|
const char* format_byte = editor->OptUpperCaseHex ? "%02X" : "%02x";
|
|
const char* format_byte_space = editor->OptUpperCaseHex ? "%02X " : "%02x ";
|
|
|
|
while (ImGuiListClipper_Step(&clipper))
|
|
for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd;
|
|
line_i++) // display only visible lines
|
|
{
|
|
size_t addr = (size_t)(line_i * editor->Cols);
|
|
igText(format_address, s.AddrDigitsCount, base_display_addr + addr);
|
|
|
|
// Draw Hexadecimal
|
|
for (int n = 0; n < editor->Cols && addr < mem_size; n++, addr++) {
|
|
float byte_pos_x = s.PosHexStart + s.HexCellWidth * n;
|
|
if (editor->OptMidColsCount > 0)
|
|
byte_pos_x += (float)(n / editor->OptMidColsCount) * s.SpacingBetweenMidCols;
|
|
igSameLine(byte_pos_x, -1.0f);
|
|
|
|
// Draw highlight
|
|
bool is_highlight_from_user_range =
|
|
(addr >= editor->HighlightMin && addr < editor->HighlightMax);
|
|
bool is_highlight_from_user_func =
|
|
(editor->HighlightFn && editor->HighlightFn(mem_data, addr));
|
|
bool is_highlight_from_preview =
|
|
(addr >= editor->DataPreviewAddr &&
|
|
addr < editor->DataPreviewAddr + preview_data_type_size);
|
|
if (is_highlight_from_user_range || is_highlight_from_user_func ||
|
|
is_highlight_from_preview) {
|
|
ImVec2 pos;
|
|
igGetCursorScreenPos(&pos);
|
|
float highlight_width = s.GlyphWidth * 2;
|
|
bool is_next_byte_highlighted =
|
|
(addr + 1 < mem_size) &&
|
|
((editor->HighlightMax != (size_t)-1 && addr + 1 < editor->HighlightMax) ||
|
|
(editor->HighlightFn && editor->HighlightFn(mem_data, addr + 1)));
|
|
if (is_next_byte_highlighted || (n + 1 == editor->Cols)) {
|
|
highlight_width = s.HexCellWidth;
|
|
if (editor->OptMidColsCount > 0 && n > 0 && (n + 1) < editor->Cols &&
|
|
((n + 1) % editor->OptMidColsCount) == 0)
|
|
highlight_width += s.SpacingBetweenMidCols;
|
|
}
|
|
|
|
vec1.x = pos.x = highlight_width;
|
|
vec1.y = pos.y + s.LineHeight;
|
|
ImDrawList_AddRectFilled(draw_list, pos, vec1, editor->HighlightColor, 0, 0);
|
|
}
|
|
|
|
if (editor->DataEditingAddr == addr) {
|
|
// Display text input on current byte
|
|
bool data_write = false;
|
|
igPushID_Ptr((void*)addr);
|
|
if (editor->DataEditingTakeFocus) {
|
|
igSetKeyboardFocusHere(0);
|
|
sprintf(editor->AddrInputBuf, format_data, s.AddrDigitsCount,
|
|
base_display_addr + addr);
|
|
sprintf(editor->DataInputBuf, format_byte,
|
|
editor->ReadFn ? editor->ReadFn(mem_data, addr) : mem_data[addr]);
|
|
}
|
|
UserData user_data;
|
|
user_data.CursorPos = -1;
|
|
sprintf(user_data.CurrentBufOverwrite, format_byte,
|
|
editor->ReadFn ? editor->ReadFn(mem_data, addr) : mem_data[addr]);
|
|
ImGuiInputTextFlags flags =
|
|
ImGuiInputTextFlags_CharsHexadecimal |
|
|
ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll |
|
|
ImGuiInputTextFlags_NoHorizontalScroll |
|
|
ImGuiInputTextFlags_CallbackAlways | ImGuiInputTextFlags_AlwaysOverwrite;
|
|
igSetNextItemWidth(s.GlyphWidth * 2);
|
|
if (igInputText("##data", editor->DataInputBuf,
|
|
IM_ARRAYSIZE(editor->DataInputBuf), flags, UserData_Callback,
|
|
&user_data))
|
|
data_write = data_next = true;
|
|
else if (!editor->DataEditingTakeFocus && !igIsItemActive())
|
|
editor->DataEditingAddr = data_editing_addr_next = (size_t)-1;
|
|
editor->DataEditingTakeFocus = false;
|
|
if (user_data.CursorPos >= 2) data_write = data_next = true;
|
|
if (data_editing_addr_next != (size_t)-1) data_write = data_next = false;
|
|
unsigned int data_input_value = 0;
|
|
if (data_write && sscanf(editor->DataInputBuf, "%X", &data_input_value) == 1) {
|
|
if (editor->WriteFn)
|
|
editor->WriteFn(mem_data, addr, (ImU8)data_input_value);
|
|
else
|
|
mem_data[addr] = (ImU8)data_input_value;
|
|
}
|
|
igPopID();
|
|
} else {
|
|
// NB: The trailing space is not visible but ensure there's no gap that the
|
|
// mouse cannot click on.
|
|
ImU8 b = editor->ReadFn ? editor->ReadFn(mem_data, addr) : mem_data[addr];
|
|
|
|
if (editor->OptShowHexII) {
|
|
if ((b >= 32 && b < 128))
|
|
igText(".%c ", b);
|
|
else if (b == 0xFF && editor->OptGreyOutZeroes)
|
|
igTextDisabled("## ");
|
|
else if (b == 0x00)
|
|
igText(" ");
|
|
else
|
|
igText(format_byte_space, b);
|
|
} else {
|
|
if (b == 0 && editor->OptGreyOutZeroes)
|
|
igTextDisabled("00 ");
|
|
else
|
|
igText(format_byte_space, b);
|
|
}
|
|
if (!editor->ReadOnly && igIsItemHovered(0) && igIsMouseClicked_Bool(0, false)) {
|
|
editor->DataEditingTakeFocus = true;
|
|
data_editing_addr_next = addr;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (editor->OptShowAscii) {
|
|
// Draw ASCII values
|
|
igSameLine(s.PosAsciiStart, -1.0f);
|
|
ImVec2 pos;
|
|
igGetCursorScreenPos(&pos);
|
|
addr = line_i * editor->Cols;
|
|
igPushID_Int(line_i);
|
|
vec1.x = s.PosAsciiEnd - s.PosAsciiStart;
|
|
vec1.y = s.LineHeight;
|
|
if (igInvisibleButton("igInvisibleButtonascii", vec1, 0)) {
|
|
editor->DataEditingAddr = editor->DataPreviewAddr =
|
|
addr + (size_t)((igGetIO()->MousePos.x - pos.x) / s.GlyphWidth);
|
|
editor->DataEditingTakeFocus = true;
|
|
}
|
|
igPopID();
|
|
for (int n = 0; n < editor->Cols && addr < mem_size; n++, addr++) {
|
|
if (addr == editor->DataEditingAddr) {
|
|
vec1.x = pos.x + s.GlyphWidth;
|
|
vec1.y = pos.y + s.LineHeight;
|
|
ImDrawList_AddRectFilled(draw_list, pos, vec1,
|
|
igGetColorU32_Col(ImGuiCol_FrameBg, 1.0f), 0, 0);
|
|
ImDrawList_AddRectFilled(draw_list, pos, vec1,
|
|
igGetColorU32_Col(ImGuiCol_TextSelectedBg, 1.0f), 0, 0);
|
|
}
|
|
unsigned char c =
|
|
editor->ReadFn ? editor->ReadFn(mem_data, addr) : mem_data[addr];
|
|
char display_c = (c < 32 || c >= 128) ? '.' : c;
|
|
|
|
ImDrawList_AddText_Vec2(draw_list, pos,
|
|
(display_c == c) ? color_text : color_disabled,
|
|
&display_c, &display_c + 1);
|
|
pos.x += s.GlyphWidth;
|
|
}
|
|
}
|
|
}
|
|
igPopStyleVar(2);
|
|
igEndChild();
|
|
|
|
// Notify the main window of our ideal child content size (FIXME: we are missing an API to get
|
|
// the contents size from the child)
|
|
igSetCursorPosX(s.WindowWidth);
|
|
|
|
if (data_next && editor->DataEditingAddr + 1 < mem_size) {
|
|
editor->DataEditingAddr = editor->DataPreviewAddr = editor->DataEditingAddr + 1;
|
|
editor->DataEditingTakeFocus = true;
|
|
} else if (data_editing_addr_next != (size_t)-1) {
|
|
editor->DataEditingAddr = editor->DataPreviewAddr = data_editing_addr_next;
|
|
editor->DataEditingTakeFocus = true;
|
|
}
|
|
|
|
const bool lock_show_data_preview = editor->OptShowDataPreview;
|
|
if (editor->OptShowOptions) {
|
|
igSeparator();
|
|
MemoryEditor_DrawOptionsLine(editor, &s, mem_data, mem_size, base_display_addr);
|
|
}
|
|
|
|
if (lock_show_data_preview) {
|
|
igSeparator();
|
|
MemoryEditor_DrawPreviewLine(editor, &s, mem_data, mem_size, base_display_addr);
|
|
}
|
|
}
|
|
|
|
void MemoryEditor_DrawOptionsLine(MemoryEditor* editor, const Sizes* s, void* mem_data,
|
|
size_t mem_size, size_t base_display_addr) {
|
|
IM_UNUSED(mem_data);
|
|
ImGuiStyle* style = igGetStyle();
|
|
const char* format_range = editor->OptUpperCaseHex
|
|
? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X"
|
|
: "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x";
|
|
|
|
ImVec2 vec1 = { 0 };
|
|
// Options menu
|
|
if (igButton("Options", vec1)) igOpenPopup_Str("context", 0);
|
|
if (igBeginPopup("context", 0)) {
|
|
igSetNextItemWidth(s->GlyphWidth * 7 + style->FramePadding.x * 2.0f);
|
|
if (igDragInt("##cols", &editor->Cols, 0.2f, 4, 32, "%d cols", 0)) {
|
|
editor->ContentsWidthChanged = true;
|
|
if (editor->Cols < 1) editor->Cols = 1;
|
|
}
|
|
igCheckbox("Show Data Preview", &editor->OptShowDataPreview);
|
|
igCheckbox("Show HexII", &editor->OptShowHexII);
|
|
if (igCheckbox("Show Ascii", &editor->OptShowAscii)) {
|
|
editor->ContentsWidthChanged = true;
|
|
}
|
|
igCheckbox("Grey out zeroes", &editor->OptGreyOutZeroes);
|
|
igCheckbox("Uppercase Hex", &editor->OptUpperCaseHex);
|
|
|
|
igEndPopup();
|
|
}
|
|
|
|
igSameLine(0, -1);
|
|
igText(format_range, s->AddrDigitsCount, base_display_addr, s->AddrDigitsCount,
|
|
base_display_addr + mem_size - 1);
|
|
igSameLine(0, -1);
|
|
igSetNextItemWidth((s->AddrDigitsCount + 1) * s->GlyphWidth + style->FramePadding.x * 2.0f);
|
|
if (igInputText("##addr", editor->AddrInputBuf, IM_ARRAYSIZE(editor->AddrInputBuf),
|
|
ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue,
|
|
NULL, NULL)) {
|
|
size_t goto_addr;
|
|
if (sscanf(editor->AddrInputBuf, "%" _PRISizeT "X", &goto_addr) == 1) {
|
|
editor->GotoAddr = goto_addr - base_display_addr;
|
|
editor->HighlightMin = editor->HighlightMax = (size_t)-1;
|
|
}
|
|
}
|
|
|
|
if (editor->GotoAddr != (size_t)-1) {
|
|
if (editor->GotoAddr < mem_size) {
|
|
vec1.x = 0;
|
|
vec1.y = 0;
|
|
igBeginChild_Str("##scrolling", vec1, false, 0);
|
|
igGetCursorStartPos(&vec1);
|
|
igSetScrollFromPosY_Float(
|
|
vec1.y + (editor->GotoAddr / editor->Cols) * igGetTextLineHeight(), 0.5f);
|
|
igEndChild();
|
|
editor->DataEditingAddr = editor->DataPreviewAddr = editor->GotoAddr;
|
|
editor->DataEditingTakeFocus = true;
|
|
}
|
|
editor->GotoAddr = (size_t)-1;
|
|
}
|
|
}
|
|
|
|
void MemoryEditor_DrawPreviewLine(MemoryEditor* editor, const Sizes* s, void* mem_data_void,
|
|
size_t mem_size, size_t base_display_addr) {
|
|
IM_UNUSED(base_display_addr);
|
|
ImVec2 vec1 = { 0 };
|
|
ImU8* mem_data = (ImU8*)mem_data_void;
|
|
ImGuiStyle* style = igGetStyle();
|
|
igAlignTextToFramePadding();
|
|
igText("Preview as:");
|
|
igSameLine(0, -1);
|
|
igSetNextItemWidth((s->GlyphWidth * 10.0f) + style->FramePadding.x * 2.0f +
|
|
style->ItemInnerSpacing.x);
|
|
if (igBeginCombo("##combo_type", MemoryEditor_DataTypeGetDesc(editor->PreviewDataType),
|
|
ImGuiComboFlags_HeightLargest)) {
|
|
for (int n = 0; n < ImGuiDataType_COUNT; n++)
|
|
if (igSelectable_Bool(MemoryEditor_DataTypeGetDesc((ImGuiDataType)n),
|
|
editor->PreviewDataType == n, 0, vec1))
|
|
editor->PreviewDataType = (ImGuiDataType)n;
|
|
igEndCombo();
|
|
}
|
|
igSameLine(0, -1);
|
|
igSetNextItemWidth((s->GlyphWidth * 6.0f) + style->FramePadding.x * 2.0f +
|
|
style->ItemInnerSpacing.x);
|
|
igCombo_Str("##combo_endianess", &editor->PreviewEndianess, "LE\0BE\0\0", -1);
|
|
|
|
char buf[128] = "";
|
|
float x = s->GlyphWidth * 6.0f;
|
|
bool has_value = editor->DataPreviewAddr != (size_t)-1;
|
|
if (has_value)
|
|
MemoryEditor_DrawPreviewData(editor, editor->DataPreviewAddr, mem_data, mem_size,
|
|
editor->PreviewDataType, DataFormat_Dec, buf,
|
|
(size_t)IM_ARRAYSIZE(buf));
|
|
igText("Dec");
|
|
igSameLine(x, -1);
|
|
igTextUnformatted(has_value ? buf : "N/A", NULL);
|
|
if (has_value)
|
|
MemoryEditor_DrawPreviewData(editor, editor->DataPreviewAddr, mem_data, mem_size,
|
|
editor->PreviewDataType, DataFormat_Hex, buf,
|
|
(size_t)IM_ARRAYSIZE(buf));
|
|
igText("Hex");
|
|
igSameLine(x, -1);
|
|
igTextUnformatted(has_value ? buf : "N/A", NULL);
|
|
if (has_value)
|
|
MemoryEditor_DrawPreviewData(editor, editor->DataPreviewAddr, mem_data, mem_size,
|
|
editor->PreviewDataType, DataFormat_Bin, buf,
|
|
(size_t)IM_ARRAYSIZE(buf));
|
|
buf[IM_ARRAYSIZE(buf) - 1] = 0;
|
|
igText("Bin");
|
|
igSameLine(x, -1);
|
|
igTextUnformatted(has_value ? buf : "N/A", NULL);
|
|
}
|
|
|
|
// Utilities for Data Preview
|
|
const char* MemoryEditor_DataTypeGetDesc(ImGuiDataType data_type) {
|
|
const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32",
|
|
"Uint32", "Int64", "Uint64", "Float", "Double" };
|
|
// IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
|
|
return descs[data_type];
|
|
}
|
|
|
|
size_t MemoryEditor_DataTypeGetSize(ImGuiDataType data_type) {
|
|
const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, sizeof(float), sizeof(double) };
|
|
// IM_ASSERT(data_type >= 0 && data_type < ImGuiDataType_COUNT);
|
|
return sizes[data_type];
|
|
}
|
|
|
|
const char* MemoryEditor_DataFormatGetDesc(DataFormat data_format) {
|
|
const char* descs[] = { "Bin", "Dec", "Hex" };
|
|
// IM_ASSERT(data_format >= 0 && data_format < DataFormat_COUNT);
|
|
return descs[data_format];
|
|
}
|
|
|
|
bool MemoryEditor_IsBigEndian() {
|
|
uint16_t x = 1;
|
|
char c[2];
|
|
memcpy(c, &x, 2);
|
|
return c[0] != 0;
|
|
}
|
|
|
|
static void* MemoryEditor_EndianessCopyBigEndian(void* _dst, void* _src, size_t s,
|
|
int is_little_endian) {
|
|
if (is_little_endian) {
|
|
uint8_t* dst = (uint8_t*)_dst;
|
|
uint8_t* src = (uint8_t*)_src + s - 1;
|
|
for (int i = 0, n = (int)s; i < n; ++i) memcpy(dst++, src--, 1);
|
|
return _dst;
|
|
} else {
|
|
return memcpy(_dst, _src, s);
|
|
}
|
|
}
|
|
|
|
static void* MemoryEditor_EndianessCopyLittleEndian(void* _dst, void* _src, size_t s,
|
|
int is_little_endian) {
|
|
if (is_little_endian) {
|
|
return memcpy(_dst, _src, s);
|
|
} else {
|
|
uint8_t* dst = (uint8_t*)_dst;
|
|
uint8_t* src = (uint8_t*)_src + s - 1;
|
|
for (int i = 0, n = (int)s; i < n; ++i) memcpy(dst++, src--, 1);
|
|
return _dst;
|
|
}
|
|
}
|
|
|
|
void* MemoryEditor_EndianessCopy(MemoryEditor* editor, void* dst, void* src, size_t size) {
|
|
static void* (*fp)(void*, void*, size_t, int) = NULL;
|
|
if (fp == NULL)
|
|
fp = MemoryEditor_IsBigEndian() ? MemoryEditor_EndianessCopyBigEndian
|
|
: MemoryEditor_EndianessCopyLittleEndian;
|
|
return fp(dst, src, size, editor->PreviewEndianess);
|
|
}
|
|
|
|
const char* MemoryEditor_FormatBinary(const uint8_t* buf, int width) {
|
|
// IM_ASSERT(width <= 64);
|
|
size_t out_n = 0;
|
|
static char out_buf[64 + 8 + 1];
|
|
int n = width / 8;
|
|
for (int j = n - 1; j >= 0; --j) {
|
|
for (int i = 0; i < 8; ++i) out_buf[out_n++] = (buf[j] & (1 << (7 - i))) ? '1' : '0';
|
|
out_buf[out_n++] = ' ';
|
|
}
|
|
// IM_ASSERT(out_n < IM_ARRAYSIZE(out_buf));
|
|
out_buf[out_n] = 0;
|
|
return out_buf;
|
|
}
|
|
|
|
// [Internal]
|
|
void MemoryEditor_DrawPreviewData(MemoryEditor* editor, size_t addr, const ImU8* mem_data,
|
|
size_t mem_size, ImGuiDataType data_type, DataFormat data_format,
|
|
char* out_buf, size_t out_buf_size) {
|
|
uint8_t buf[8];
|
|
size_t elem_size = MemoryEditor_DataTypeGetSize(data_type);
|
|
size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size;
|
|
if (editor->ReadFn)
|
|
for (int i = 0, n = (int)size; i < n; ++i) buf[i] = editor->ReadFn(mem_data, addr + i);
|
|
else
|
|
memcpy(buf, mem_data + addr, size);
|
|
|
|
if (data_format == DataFormat_Bin) {
|
|
uint8_t binbuf[8];
|
|
MemoryEditor_EndianessCopy(editor, binbuf, buf, size);
|
|
ImSnprintf(out_buf, out_buf_size, "%s", MemoryEditor_FormatBinary(binbuf, (int)size * 8));
|
|
return;
|
|
}
|
|
|
|
out_buf[0] = 0;
|
|
switch (data_type) {
|
|
case ImGuiDataType_S8: {
|
|
int8_t int8 = 0;
|
|
MemoryEditor_EndianessCopy(editor, &int8, buf, size);
|
|
if (data_format == DataFormat_Dec) {
|
|
ImSnprintf(out_buf, out_buf_size, "%hhd", int8);
|
|
return;
|
|
}
|
|
if (data_format == DataFormat_Hex) {
|
|
ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ImGuiDataType_U8: {
|
|
uint8_t uint8 = 0;
|
|
MemoryEditor_EndianessCopy(editor, &uint8, buf, size);
|
|
if (data_format == DataFormat_Dec) {
|
|
ImSnprintf(out_buf, out_buf_size, "%hhu", uint8);
|
|
return;
|
|
}
|
|
if (data_format == DataFormat_Hex) {
|
|
ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ImGuiDataType_S16: {
|
|
int16_t int16 = 0;
|
|
MemoryEditor_EndianessCopy(editor, &int16, buf, size);
|
|
if (data_format == DataFormat_Dec) {
|
|
ImSnprintf(out_buf, out_buf_size, "%hd", int16);
|
|
return;
|
|
}
|
|
if (data_format == DataFormat_Hex) {
|
|
ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ImGuiDataType_U16: {
|
|
uint16_t uint16 = 0;
|
|
MemoryEditor_EndianessCopy(editor, &uint16, buf, size);
|
|
if (data_format == DataFormat_Dec) {
|
|
ImSnprintf(out_buf, out_buf_size, "%hu", uint16);
|
|
return;
|
|
}
|
|
if (data_format == DataFormat_Hex) {
|
|
ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ImGuiDataType_S32: {
|
|
int32_t int32 = 0;
|
|
MemoryEditor_EndianessCopy(editor, &int32, buf, size);
|
|
if (data_format == DataFormat_Dec) {
|
|
ImSnprintf(out_buf, out_buf_size, "%d", int32);
|
|
return;
|
|
}
|
|
if (data_format == DataFormat_Hex) {
|
|
ImSnprintf(out_buf, out_buf_size, "0x%08x", int32);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ImGuiDataType_U32: {
|
|
uint32_t uint32 = 0;
|
|
MemoryEditor_EndianessCopy(editor, &uint32, buf, size);
|
|
if (data_format == DataFormat_Dec) {
|
|
ImSnprintf(out_buf, out_buf_size, "%u", uint32);
|
|
return;
|
|
}
|
|
if (data_format == DataFormat_Hex) {
|
|
ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ImGuiDataType_S64: {
|
|
int64_t int64 = 0;
|
|
MemoryEditor_EndianessCopy(editor, &int64, buf, size);
|
|
if (data_format == DataFormat_Dec) {
|
|
ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64);
|
|
return;
|
|
}
|
|
if (data_format == DataFormat_Hex) {
|
|
ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ImGuiDataType_U64: {
|
|
uint64_t uint64 = 0;
|
|
MemoryEditor_EndianessCopy(editor, &uint64, buf, size);
|
|
if (data_format == DataFormat_Dec) {
|
|
ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64);
|
|
return;
|
|
}
|
|
if (data_format == DataFormat_Hex) {
|
|
ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ImGuiDataType_Float: {
|
|
float float32 = 0.0f;
|
|
MemoryEditor_EndianessCopy(editor, &float32, buf, size);
|
|
if (data_format == DataFormat_Dec) {
|
|
ImSnprintf(out_buf, out_buf_size, "%f", float32);
|
|
return;
|
|
}
|
|
if (data_format == DataFormat_Hex) {
|
|
ImSnprintf(out_buf, out_buf_size, "%a", float32);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ImGuiDataType_Double: {
|
|
double float64 = 0.0;
|
|
MemoryEditor_EndianessCopy(editor, &float64, buf, size);
|
|
if (data_format == DataFormat_Dec) {
|
|
ImSnprintf(out_buf, out_buf_size, "%f", float64);
|
|
return;
|
|
}
|
|
if (data_format == DataFormat_Hex) {
|
|
ImSnprintf(out_buf, out_buf_size, "%a", float64);
|
|
return;
|
|
}
|
|
break;
|
|
}
|
|
case ImGuiDataType_COUNT:
|
|
break;
|
|
}
|
|
} |