1 // Mini memory editor for Dear ImGui (to embed in your game/tools) 2 // Get latest version at http://www.github.com/ocornut/imgui_club 3 // 4 // Right-click anywhere to access the Options menu! 5 // You can adjust the keyboard repeat delay/rate in ImGuiIO. 6 // The code assume a mono-space font for simplicity! 7 // If you don't use the default font, use ImGui::PushFont()/PopFont() to switch to a mono-space font before caling this. 8 // 9 // Usage: 10 // // Create a window and draw memory editor inside it: 11 // static MemoryEditor mem_edit_1; 12 // static char data[0x10000]; 13 // size_t data_size = 0x10000; 14 // mem_edit_1.DrawWindow("Memory Editor", data, data_size); 15 // 16 // Usage: 17 // // If you already have a window, use DrawContents() instead: 18 // static MemoryEditor mem_edit_2; 19 // ImGui::Begin("MyWindow") 20 // mem_edit_2.DrawContents(this, sizeof(*this), (size_t)this); 21 // ImGui::End(); 22 // 23 // Changelog: 24 // - v0.10: initial version 25 // - v0.23 (2017/08/17): added to github. fixed right-arrow triggering a byte write. 26 // - v0.24 (2018/06/02): changed DragInt("Rows" to use a %d data format (which is desirable since imgui 1.61). 27 // - v0.25 (2018/07/11): fixed wording: all occurrences of "Rows" renamed to "Columns". 28 // - v0.26 (2018/08/02): fixed clicking on hex region 29 // - v0.30 (2018/08/02): added data preview for common data types 30 // - v0.31 (2018/10/10): added OptUpperCaseHex option to select lower/upper casing display [@samhocevar] 31 // - v0.32 (2018/10/10): changed signatures to use void* instead of unsigned char* 32 // - v0.33 (2018/10/10): added OptShowOptions option to hide all the interactive option setting. 33 // - v0.34 (2019/05/07): binary preview now applies endianness setting [@nicolasnoble] 34 // - v0.35 (2020/01/29): using ImGuiDataType available since Dear ImGui 1.69. 35 // - v0.36 (2020/05/05): minor tweaks, minor refactor. 36 // - v0.40 (2020/10/04): fix misuse of ImGuiListClipper API, broke with Dear ImGui 1.79. made cursor position appears on left-side of edit box. option popup appears on mouse release. fix MSVC warnings where _CRT_SECURE_NO_WARNINGS wasn't working in recent versions. 37 // - v0.41 (2020/10/05): fix when using with keyboard/gamepad navigation enabled. 38 // - v0.42 (2020/10/14): fix for . character in ASCII view always being greyed out. 39 // 40 // Todo/Bugs: 41 // - This is generally old code, it should work but please don't use this as reference! 42 // - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame. 43 // - Using InputText() is awkward and maybe overkill here, consider implementing something custom. 44 45 #pragma once 46 47 #include <stdio.h> // sprintf, scanf 48 #include <stdint.h> // uint8_t, etc. 49 #include <hex/helpers/utils.hpp> 50 51 #include <hex/views/view.hpp> 52 53 #include <string> 54 55 #ifdef _MSC_VER 56 #define _PRISizeT "I" 57 #define ImSnprintf _snprintf 58 #else 59 #define _PRISizeT "z" 60 #define ImSnprintf snprintf 61 #endif 62 63 #ifdef _MSC_VER 64 #pragma warning (push) 65 #pragma warning (disable: 4996) // warning C4996: 'sprintf': This function or variable may be unsafe. 66 #endif 67 68 ImU32 ImAlphaBlendColors(ImU32 col_a, ImU32 col_b); 69 70 struct MemoryEditor 71 { 72 enum DataFormat 73 { 74 DataFormat_Bin = 0, 75 DataFormat_Dec = 1, 76 DataFormat_Hex = 2, 77 DataFormat_COUNT 78 }; 79 80 struct DecodeData { 81 std::string data; 82 size_t advance; 83 ImColor color; 84 }; 85 86 // Settings 87 bool ReadOnly; // = false // disable any editing. 88 int Cols; // = 16 // number of columns to display. 89 bool OptShowOptions; // = true // display options button/context menu. when disabled, options will be locked unless you provide your own UI for them. 90 bool OptShowHexII; // = false // display values in HexII representation instead of regular hexadecimal: hide null/zero bytes, ascii values as ".X". 91 bool OptShowAscii; // = true // display ASCII representation on the right side. 92 bool OptShowAdvancedDecoding; // = true // display advanced decoding data on the right side. 93 bool OptGreyOutZeroes; // = true // display null/zero bytes using the TextDisabled color. 94 bool OptUpperCaseHex; // = true // display hexadecimal values as "FF" instead of "ff". 95 int OptMidColsCount; // = 8 // set to 0 to disable extra spacing between every mid-cols. 96 int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr). 97 ImU32 HighlightColor; // // background color of highlighted bytes. 98 ImU8 (*ReadFn)(const ImU8* data, size_t off); // = 0 // optional handler to read bytes. 99 void (*WriteFn)(ImU8* data, size_t off, ImU8 d); // = 0 // optional handler to write bytes. 100 bool (*HighlightFn)(const ImU8* data, size_t off, bool next);//= 0 // optional handler to return Highlight property (to support non-contiguous highlighting). 101 void (*HoverFn)(const ImU8 *data, size_t off); 102 DecodeData (*DecodeFn)(const ImU8 *data, size_t off); 103 104 // [Internal State] 105 bool ContentsWidthChanged; 106 size_t DataPreviewAddr; 107 size_t DataPreviewAddrOld; 108 size_t DataPreviewAddrEnd; 109 size_t DataPreviewAddrEndOld; 110 size_t DataEditingAddr; 111 bool DataEditingTakeFocus; 112 char DataInputBuf[32]; 113 char AddrInputBuf[32]; 114 size_t GotoAddr; 115 size_t HighlightMin, HighlightMax; 116 int PreviewEndianess; 117 ImGuiDataType PreviewDataType; 118 MemoryEditorMemoryEditor119 MemoryEditor() 120 { 121 // Settings 122 ReadOnly = false; 123 Cols = 16; 124 OptShowOptions = true; 125 OptShowHexII = false; 126 OptShowAscii = true; 127 OptShowAdvancedDecoding = true; 128 OptGreyOutZeroes = true; 129 OptUpperCaseHex = true; 130 OptMidColsCount = 8; 131 OptAddrDigitsCount = 0; 132 HighlightColor = IM_COL32(255, 255, 255, 50); 133 ReadFn = NULL; 134 WriteFn = NULL; 135 HighlightFn = NULL; 136 HoverFn = NULL; 137 DecodeFn = NULL; 138 139 // State/Internals 140 ContentsWidthChanged = false; 141 DataPreviewAddr = DataEditingAddr = DataPreviewAddrEnd = (size_t)-1; 142 DataPreviewAddrOld = DataPreviewAddrEndOld = (size_t)-1; 143 DataEditingTakeFocus = false; 144 memset(DataInputBuf, 0, sizeof(DataInputBuf)); 145 memset(AddrInputBuf, 0, sizeof(AddrInputBuf)); 146 GotoAddr = (size_t)-1; 147 HighlightMin = HighlightMax = (size_t)-1; 148 PreviewEndianess = 0; 149 PreviewDataType = ImGuiDataType_S32; 150 } 151 GotoAddrAndHighlightMemoryEditor152 void GotoAddrAndHighlight(size_t addr_min, size_t addr_max) 153 { 154 GotoAddr = addr_min; 155 HighlightMin = addr_min; 156 HighlightMax = addr_max; 157 } 158 159 struct Sizes 160 { 161 int AddrDigitsCount; 162 float LineHeight; 163 float GlyphWidth; 164 float HexCellWidth; 165 float SpacingBetweenMidCols; 166 float PosHexStart; 167 float PosHexEnd; 168 float PosAsciiStart; 169 float PosAsciiEnd; 170 float PosDecodingStart; 171 float PosDecodingEnd; 172 float WindowWidth; 173 SizesMemoryEditor::Sizes174 Sizes() { memset(this, 0, sizeof(*this)); } 175 }; 176 CalcSizesMemoryEditor177 void CalcSizes(Sizes& s, size_t mem_size, size_t base_display_addr) 178 { 179 ImGuiStyle& style = ImGui::GetStyle(); 180 s.AddrDigitsCount = OptAddrDigitsCount; 181 if (s.AddrDigitsCount == 0) 182 for (size_t n = base_display_addr + mem_size - 1; n > 0; n >>= 4) 183 s.AddrDigitsCount++; 184 s.LineHeight = ImGui::GetTextLineHeight(); 185 s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space 186 s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere 187 s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing 188 s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth; 189 s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols); 190 s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd; 191 192 if (OptShowAscii && OptShowAdvancedDecoding) { 193 s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1; 194 if (OptMidColsCount > 0) 195 s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; 196 s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth; 197 198 s.PosDecodingStart = s.PosAsciiEnd + s.GlyphWidth * 1; 199 if (OptMidColsCount > 0) 200 s.PosDecodingStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; 201 s.PosDecodingEnd = s.PosDecodingStart + Cols * s.GlyphWidth; 202 } else if (OptShowAscii) { 203 s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1; 204 if (OptMidColsCount > 0) 205 s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; 206 s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth; 207 } else if (OptShowAdvancedDecoding) { 208 s.PosDecodingStart = s.PosHexEnd + s.GlyphWidth * 1; 209 if (OptMidColsCount > 0) 210 s.PosDecodingStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; 211 s.PosDecodingEnd = s.PosDecodingStart + Cols * s.GlyphWidth; 212 } 213 s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth; 214 } 215 216 // Standalone Memory Editor window 217 void DrawWindow(const char* title, bool *p_open, void* mem_data, size_t mem_size, size_t base_display_addr = 0x0000) 218 { 219 Sizes s; 220 CalcSizes(s, mem_size, base_display_addr); 221 222 if (ImGui::Begin(title, p_open, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoNavInputs)) 223 { 224 if (DataPreviewAddr != DataPreviewAddrOld || DataPreviewAddrEnd != DataPreviewAddrEndOld) { 225 hex::Region selectionRegion = { std::min(DataPreviewAddr, DataPreviewAddrEnd), std::max(DataPreviewAddr, DataPreviewAddrEnd) - std::min(DataPreviewAddr, DataPreviewAddrEnd) }; 226 hex::View::postEvent(hex::Events::RegionSelected, selectionRegion); 227 } 228 229 DataPreviewAddrOld = DataPreviewAddr; 230 DataPreviewAddrEndOld = DataPreviewAddrEnd; 231 232 DrawContents(mem_data, mem_size, base_display_addr); 233 if (ContentsWidthChanged) 234 { 235 CalcSizes(s, mem_size, base_display_addr); 236 ImGui::SetWindowSize(ImVec2(s.WindowWidth, ImGui::GetWindowSize().y)); 237 } 238 } 239 ImGui::End(); 240 241 } 242 243 // Memory Editor contents only 244 void DrawContents(void* mem_data_void, size_t mem_size, size_t base_display_addr = 0x0000) 245 { 246 if (Cols < 1) 247 Cols = 1; 248 249 ImU8* mem_data = (ImU8*)mem_data_void; 250 Sizes s; 251 CalcSizes(s, mem_size, base_display_addr); 252 ImGuiStyle& style = ImGui::GetStyle(); 253 254 // We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window. 255 // 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. 256 const float height_separator = style.ItemSpacing.y; 257 float footer_height = 0; 258 if (OptShowOptions) 259 footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1; 260 261 ImGui::BeginChild("offset", ImVec2(0, s.LineHeight), false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav); 262 ImGui::Text("%*c ", s.AddrDigitsCount, ' '); 263 for (int i = 0; i < Cols; i++) { 264 float byte_pos_x = s.PosHexStart + s.HexCellWidth * i; 265 if (OptMidColsCount > 0) 266 byte_pos_x += (float)(i / OptMidColsCount) * s.SpacingBetweenMidCols; 267 ImGui::SameLine(byte_pos_x); 268 ImGui::Text("%02X", i); 269 } 270 ImGui::EndChild(); 271 272 ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoNav); 273 274 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); 275 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); 276 277 278 279 // We are not really using the clipper API correctly here, because we rely on visible_start_addr/visible_end_addr for our scrolling function. 280 ImGuiListClipper clipper; 281 ImDrawList* draw_list = ImGui::GetWindowDrawList(); 282 283 const int line_total_count = (int)((mem_size + Cols - 1) / Cols); 284 clipper.Begin(line_total_count, s.LineHeight); 285 clipper.Step(); 286 const size_t visible_start_addr = clipper.DisplayStart * Cols; 287 const size_t visible_end_addr = clipper.DisplayEnd * Cols; 288 const size_t visible_count = visible_end_addr - visible_start_addr; 289 290 bool data_next = false; 291 292 if (DataEditingAddr >= mem_size) 293 DataEditingAddr = (size_t)-1; 294 if (DataPreviewAddr >= mem_size) 295 DataPreviewAddr = (size_t)-1; 296 if (DataPreviewAddrEnd >= mem_size) 297 DataPreviewAddrEnd = (size_t)-1; 298 299 size_t data_editing_addr_backup = DataEditingAddr; 300 size_t data_preview_addr_backup = DataPreviewAddr; 301 size_t data_editing_addr_next = (size_t)-1; 302 size_t data_preview_addr_next = (size_t)-1; 303 304 if (ImGui::IsWindowFocused()) { 305 if (DataEditingAddr != (size_t)-1) 306 { 307 // 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) 308 if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && DataEditingAddr >= (size_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; DataEditingTakeFocus = true; } 309 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && DataEditingAddr < mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; DataEditingTakeFocus = true; } 310 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && DataEditingAddr > 0) { data_editing_addr_next = DataEditingAddr - 1; DataEditingTakeFocus = true; } 311 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && DataEditingAddr < mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; DataEditingTakeFocus = true; } 312 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp)) && DataEditingAddr > 0) { data_editing_addr_next = std::max(s64(0), s64(DataEditingAddr) - s64(visible_count)); DataEditingTakeFocus = true; } 313 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown)) && DataEditingAddr < mem_size - 1) { data_editing_addr_next = std::min(s64(mem_size - 1), s64(DataEditingAddr) + s64(visible_count)); DataEditingTakeFocus = true; } 314 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)) && DataEditingAddr > 0) { data_editing_addr_next = 0; DataEditingTakeFocus = true; } 315 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)) && DataEditingAddr < mem_size - 1) { data_editing_addr_next = mem_size - 1; DataEditingTakeFocus = true; } 316 } else if (DataPreviewAddr != -1) { 317 // 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) 318 if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && DataPreviewAddr >= (size_t)Cols) { DataPreviewAddr = data_preview_addr_next = DataPreviewAddr - Cols; if (!ImGui::GetIO().KeyShift) DataPreviewAddrEnd = DataPreviewAddr; } 319 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && DataPreviewAddr < mem_size - Cols) { DataPreviewAddr = data_preview_addr_next = DataPreviewAddr + Cols; if (!ImGui::GetIO().KeyShift) DataPreviewAddrEnd = DataPreviewAddr; } 320 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && DataPreviewAddr > 0) { DataPreviewAddr = data_preview_addr_next = DataPreviewAddr - 1; if (!ImGui::GetIO().KeyShift) DataPreviewAddrEnd = DataPreviewAddr; } 321 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && DataPreviewAddr < mem_size - 1) { DataPreviewAddr = data_preview_addr_next = DataPreviewAddr + 1; if (!ImGui::GetIO().KeyShift) DataPreviewAddrEnd = DataPreviewAddr; } 322 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageUp)) && DataPreviewAddr > 0) { DataPreviewAddr = data_preview_addr_next = std::max(s64(0), s64(DataPreviewAddr) - s64(visible_count)); if (!ImGui::GetIO().KeyShift) DataPreviewAddrEnd = DataPreviewAddr; } 323 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_PageDown)) && DataPreviewAddr < mem_size - 1) { DataPreviewAddr = data_preview_addr_next = std::min(s64(mem_size - 1), s64(DataPreviewAddr) + s64(visible_count)); if (!ImGui::GetIO().KeyShift) DataPreviewAddrEnd = DataPreviewAddr; } 324 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_Home)) && DataPreviewAddr > 0) { DataPreviewAddr = data_preview_addr_next = 0; if (!ImGui::GetIO().KeyShift) DataPreviewAddrEnd = DataPreviewAddr; } 325 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_End)) && DataPreviewAddr < mem_size - 1) { DataPreviewAddr = data_preview_addr_next = mem_size - 1; if (!ImGui::GetIO().KeyShift) DataPreviewAddrEnd = DataPreviewAddr; } 326 } 327 } 328 329 if (data_preview_addr_next != (size_t)-1 && (data_preview_addr_next / Cols) != (data_preview_addr_backup / Cols)) 330 { 331 // Track cursor movements 332 const int scroll_offset = ((int)(data_preview_addr_next / Cols) - (int)(data_preview_addr_backup / Cols)); 333 const bool scroll_desired = (scroll_offset < 0 && data_preview_addr_next < visible_start_addr + Cols * 2) || (scroll_offset > 0 && data_preview_addr_next > visible_end_addr - Cols * 2); 334 if (scroll_desired) 335 ImGui::SetScrollY(ImGui::GetScrollY() + scroll_offset * s.LineHeight); 336 } 337 if (data_editing_addr_next != (size_t)-1 && (data_editing_addr_next / Cols) != (data_editing_addr_backup / Cols)) 338 { 339 // Track cursor movements 340 const int scroll_offset = ((int)(data_editing_addr_next / Cols) - (int)(data_editing_addr_backup / Cols)); 341 const bool scroll_desired = (scroll_offset < 0 && data_editing_addr_next < visible_start_addr + Cols * 2) || (scroll_offset > 0 && data_editing_addr_next > visible_end_addr - Cols * 2); 342 if (scroll_desired) 343 ImGui::SetScrollY(ImGui::GetScrollY() + scroll_offset * s.LineHeight); 344 } 345 346 // Draw vertical separator 347 ImVec2 window_pos = ImGui::GetWindowPos(); 348 if (OptShowAscii) 349 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)); 350 if (OptShowAdvancedDecoding) 351 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)); 352 353 const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text); 354 const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text; 355 356 const char* format_address = OptUpperCaseHex ? "%0*" _PRISizeT "X: " : "%0*" _PRISizeT "x: "; 357 const char* format_data = OptUpperCaseHex ? "%0*" _PRISizeT "X" : "%0*" _PRISizeT "x"; 358 const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x"; 359 const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x "; 360 361 bool tooltipShown = false; 362 for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines 363 { 364 size_t addr = (size_t)(line_i * Cols); 365 ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr); 366 367 // Draw Hexadecimal 368 for (int n = 0; n < Cols && addr < mem_size; n++, addr++) 369 { 370 float byte_pos_x = s.PosHexStart + s.HexCellWidth * n; 371 if (OptMidColsCount > 0) 372 byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols; 373 ImGui::SameLine(byte_pos_x); 374 375 // Draw highlight 376 bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax); 377 bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr, false)); 378 bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr <= DataPreviewAddrEnd) || (addr >= DataPreviewAddrEnd && addr <= DataPreviewAddr); 379 if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview) 380 { 381 ImVec2 pos = ImGui::GetCursorScreenPos(); 382 float highlight_width = s.GlyphWidth * 2; 383 bool is_next_byte_highlighted = (addr + 1 < mem_size) && 384 ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || 385 (HighlightFn && HighlightFn(mem_data, addr + 1, true)) || 386 ((addr + 1) >= DataPreviewAddr && (addr + 1) <= DataPreviewAddrEnd) || ((addr + 1) >= DataPreviewAddrEnd && (addr + 1) <= DataPreviewAddr)); 387 if (is_next_byte_highlighted) 388 { 389 highlight_width = s.HexCellWidth; 390 if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0) 391 highlight_width += s.SpacingBetweenMidCols; 392 } 393 394 ImU32 color = HighlightColor; 395 if ((is_highlight_from_user_range + is_highlight_from_user_func + is_highlight_from_preview) > 1) 396 color = (ImAlphaBlendColors(HighlightColor, 0x60C08080) & 0x00FFFFFF) | 0x90000000; 397 398 draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), color); 399 } 400 401 if (DataEditingAddr == addr) 402 { 403 // Display text input on current byte 404 bool data_write = false; 405 ImGui::PushID((void*)addr); 406 if (DataEditingTakeFocus) 407 { 408 ImGui::SetKeyboardFocusHere(); 409 ImGui::CaptureKeyboardFromApp(true); 410 sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr); 411 sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); 412 } 413 ImGui::PushItemWidth(s.GlyphWidth * 2); 414 struct UserData 415 { 416 // 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. CallbackMemoryEditor::UserData417 static int Callback(ImGuiInputTextCallbackData* data) 418 { 419 UserData* user_data = (UserData*)data->UserData; 420 if (!data->HasSelection()) 421 user_data->CursorPos = data->CursorPos; 422 if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen) 423 { 424 // When not editing a byte, always rewrite its content (this is a bit tricky, since InputText technically "owns" the master copy of the buffer we edit it in there) 425 data->DeleteChars(0, data->BufTextLen); 426 data->InsertChars(0, user_data->CurrentBufOverwrite); 427 data->SelectionStart = 0; 428 data->SelectionEnd = 2; 429 data->CursorPos = 0; 430 } 431 return 0; 432 } 433 char CurrentBufOverwrite[3]; // Input 434 int CursorPos; // Output 435 }; 436 UserData user_data; 437 user_data.CursorPos = -1; 438 sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); 439 ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_AlwaysInsertMode | ImGuiInputTextFlags_CallbackAlways; 440 if (ImGui::InputText("##data", DataInputBuf, 32, flags, UserData::Callback, &user_data)) 441 data_write = data_next = true; 442 else if (!DataEditingTakeFocus && !ImGui::IsItemActive()) 443 DataEditingAddr = data_editing_addr_next = (size_t)-1; 444 DataEditingTakeFocus = false; 445 ImGui::PopItemWidth(); 446 if (user_data.CursorPos >= 2) 447 data_write = data_next = true; 448 if (data_editing_addr_next != (size_t)-1) 449 data_write = data_next = false; 450 unsigned int data_input_value = 0; 451 if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1) 452 { 453 if (WriteFn) 454 WriteFn(mem_data, addr, (ImU8)data_input_value); 455 else 456 mem_data[addr] = (ImU8)data_input_value; 457 } 458 ImGui::PopID(); 459 } 460 else 461 { 462 // NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on. 463 ImU8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; 464 465 if (OptShowHexII) 466 { 467 if ((b >= 32 && b < 128)) 468 ImGui::Text(".%c ", b); 469 else if (b == 0xFF && OptGreyOutZeroes) 470 ImGui::TextDisabled("## "); 471 else if (b == 0x00) 472 ImGui::Text(" "); 473 else 474 ImGui::Text(format_byte_space, b); 475 } 476 else 477 { 478 if (b == 0 && OptGreyOutZeroes) 479 ImGui::TextDisabled("00 "); 480 else 481 ImGui::Text(format_byte_space, b); 482 } 483 if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0) && !ImGui::GetIO().KeyShift) 484 { 485 if (!ReadOnly && ImGui::IsMouseDoubleClicked(0)) { 486 DataEditingTakeFocus = true; 487 data_editing_addr_next = addr; 488 } 489 490 DataPreviewAddr = addr; 491 DataPreviewAddrEnd = addr; 492 } 493 if (ImGui::IsItemHovered() && ((ImGui::IsMouseClicked(0) && ImGui::GetIO().KeyShift) || ImGui::IsMouseDragging(0))) { 494 DataPreviewAddrEnd = addr; 495 } 496 if (ImGui::IsItemHovered() && !tooltipShown) { 497 if (HoverFn) { 498 HoverFn(mem_data, addr); 499 tooltipShown = true; 500 } 501 } 502 } 503 } 504 505 if (OptShowAscii) 506 { 507 // Draw ASCII values 508 ImGui::SameLine(s.PosAsciiStart); 509 ImVec2 pos = ImGui::GetCursorScreenPos(); 510 addr = line_i * Cols; 511 512 ImGui::PushID(-1); 513 ImGui::SameLine(); 514 ImGui::Dummy(ImVec2(s.GlyphWidth, s.LineHeight)); 515 516 ImGui::PopID(); 517 518 for (int n = 0; n < Cols && addr < mem_size; n++, addr++) 519 { 520 if (addr == DataEditingAddr) 521 { 522 draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg)); 523 draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg)); 524 } 525 unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; 526 char display_c = (c < 32 || c >= 128) ? '.' : c; 527 draw_list->AddText(pos, (display_c == c) ? color_text : color_disabled, &display_c, &display_c + 1); 528 529 // Draw highlight 530 bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax); 531 bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr, false)); 532 bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr <= DataPreviewAddrEnd) || (addr >= DataPreviewAddrEnd && addr <= DataPreviewAddr); 533 if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview) 534 { 535 ImU32 color = HighlightColor; 536 if ((is_highlight_from_user_range + is_highlight_from_user_func + is_highlight_from_preview) > 1) 537 color = (ImAlphaBlendColors(HighlightColor, 0x60C08080) & 0x00FFFFFF) | 0x90000000; 538 539 draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), color); 540 } 541 542 543 ImGui::PushID(line_i * Cols + n); 544 ImGui::SameLine(); 545 ImGui::Dummy(ImVec2(s.GlyphWidth, s.LineHeight)); 546 547 ImGui::PopID(); 548 549 if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0) && !ImGui::GetIO().KeyShift) 550 { 551 if (!ReadOnly && ImGui::IsMouseDoubleClicked(0)) { 552 DataEditingTakeFocus = true; 553 data_editing_addr_next = addr; 554 } 555 556 DataPreviewAddr = addr; 557 DataPreviewAddrEnd = addr; 558 559 } 560 if (ImGui::IsItemHovered() && ((ImGui::IsMouseClicked(0) && ImGui::GetIO().KeyShift) || ImGui::IsMouseDragging(0))) { 561 DataPreviewAddrEnd = addr; 562 } 563 564 pos.x += s.GlyphWidth; 565 } 566 567 ImGui::PushID(-1); 568 ImGui::SameLine(); 569 ImGui::Dummy(ImVec2(s.GlyphWidth, s.LineHeight)); 570 571 ImGui::PopID(); 572 } 573 574 if (OptShowAdvancedDecoding && DecodeFn) { 575 // Draw decoded bytes 576 ImGui::SameLine(s.PosDecodingStart); 577 ImVec2 pos = ImGui::GetCursorScreenPos(); 578 addr = line_i * Cols; 579 580 ImGui::PushID(-1); 581 ImGui::SameLine(); 582 ImGui::Dummy(ImVec2(s.GlyphWidth, s.LineHeight)); 583 584 ImGui::PopID(); 585 586 for (int n = 0; n < Cols && addr < mem_size;) 587 { 588 auto decodedData = DecodeFn(mem_data, addr); 589 590 auto displayData = decodedData.data; 591 auto glyphWidth = ImGui::CalcTextSize(displayData.c_str()).x + 1; 592 593 if (addr == DataEditingAddr) 594 { 595 draw_list->AddRectFilled(pos, ImVec2(pos.x + glyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg)); 596 draw_list->AddRectFilled(pos, ImVec2(pos.x + glyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg)); 597 } 598 599 draw_list->AddText(pos, decodedData.color, displayData.c_str(), displayData.c_str() + displayData.length()); 600 601 // Draw highlight 602 bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax); 603 bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr, false)); 604 bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr <= DataPreviewAddrEnd) || (addr >= DataPreviewAddrEnd && addr <= DataPreviewAddr); 605 if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview) 606 { 607 ImU32 color = HighlightColor; 608 if ((is_highlight_from_user_range + is_highlight_from_user_func + is_highlight_from_preview) > 1) 609 color = (ImAlphaBlendColors(HighlightColor, 0x60C08080) & 0x00FFFFFF) | 0x90000000; 610 611 draw_list->AddRectFilled(pos, ImVec2(pos.x + glyphWidth, pos.y + s.LineHeight), color); 612 } 613 614 615 ImGui::PushID(line_i * Cols + n); 616 ImGui::SameLine(); 617 ImGui::Dummy(ImVec2(glyphWidth, s.LineHeight)); 618 619 ImGui::PopID(); 620 621 if (ImGui::IsItemHovered() && ImGui::IsMouseClicked(0) && !ImGui::GetIO().KeyShift) 622 { 623 if (!ReadOnly && ImGui::IsMouseDoubleClicked(0)) { 624 DataEditingTakeFocus = true; 625 data_editing_addr_next = addr; 626 } 627 628 DataPreviewAddr = addr; 629 DataPreviewAddrEnd = addr; 630 631 } 632 if (ImGui::IsItemHovered() && ((ImGui::IsMouseClicked(0) && ImGui::GetIO().KeyShift) || ImGui::IsMouseDragging(0))) { 633 DataPreviewAddrEnd = addr; 634 } 635 636 pos.x += glyphWidth; 637 638 if (addr <= 1) { 639 n++; 640 addr++; 641 } else { 642 n += decodedData.advance; 643 addr += decodedData.advance; 644 } 645 } 646 } 647 } 648 IM_ASSERT(clipper.Step() == false); 649 clipper.End(); 650 ImGui::PopStyleVar(2); 651 ImGui::EndChild(); 652 653 if (data_next && DataEditingAddr < mem_size) 654 { 655 DataEditingAddr = DataPreviewAddr = DataEditingAddr + 1; 656 DataEditingTakeFocus = true; 657 } 658 else if (data_editing_addr_next != (size_t)-1) 659 { 660 DataEditingAddr = DataPreviewAddr = DataPreviewAddrEnd = data_editing_addr_next; 661 } 662 663 if (OptShowOptions) 664 { 665 ImGui::Separator(); 666 DrawOptionsLine(s, mem_data, mem_size, base_display_addr); 667 } 668 669 // Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child) 670 ImGui::SetCursorPosX(s.WindowWidth); 671 } 672 DrawOptionsLineMemoryEditor673 void DrawOptionsLine(const Sizes& s, void* mem_data, size_t mem_size, size_t base_display_addr) 674 { 675 IM_UNUSED(mem_data); 676 ImGuiStyle& style = ImGui::GetStyle(); 677 const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x"; 678 const char* format_selection = OptUpperCaseHex ? "Selection %0*" _PRISizeT "X..%0*" _PRISizeT "X (%ld %s)" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x (%ld %s)"; 679 680 // Options menu 681 if (ImGui::Button("Options")) 682 ImGui::OpenPopup("options"); 683 684 if (ImGui::BeginPopup("options")) { 685 ImGui::PushItemWidth(ImGui::CalcTextSize("00 cols").x * 1.1f); 686 if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; if (Cols < 1) Cols = 1; } 687 ImGui::PopItemWidth(); 688 ImGui::Checkbox("Show HexII", &OptShowHexII); 689 if (ImGui::Checkbox("Show Ascii", &OptShowAscii)) { ContentsWidthChanged = true; } 690 if (ImGui::Checkbox("Show Advanced Decoding", &OptShowAdvancedDecoding)) { ContentsWidthChanged = true; } 691 ImGui::Checkbox("Grey out zeroes", &OptGreyOutZeroes); 692 ImGui::Checkbox("Uppercase Hex", &OptUpperCaseHex); 693 694 ImGui::EndPopup(); 695 } 696 697 ImGui::SameLine(); 698 ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1); 699 if (DataPreviewAddr != (size_t)-1 && DataPreviewAddrEnd != (size_t)-1) { 700 ImGui::SameLine(); 701 ImGui::Spacing(); 702 ImGui::SameLine(); 703 704 auto selectionStart = std::min(DataPreviewAddr, DataPreviewAddrEnd); 705 auto selectionEnd = std::max(DataPreviewAddr, DataPreviewAddrEnd); 706 707 size_t regionSize = (selectionEnd - selectionStart) + 1; 708 ImGui::Text(format_selection, s.AddrDigitsCount, base_display_addr + selectionStart, s.AddrDigitsCount, base_display_addr + selectionEnd, regionSize, regionSize == 1 ? "byte" : "bytes"); 709 } 710 711 if (GotoAddr != (size_t)-1) 712 { 713 if (GotoAddr < mem_size) 714 { 715 ImGui::BeginChild("##scrolling"); 716 ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight()); 717 ImGui::EndChild(); 718 DataEditingAddr = DataPreviewAddr = DataPreviewAddrEnd = GotoAddr; 719 DataEditingTakeFocus = true; 720 } 721 GotoAddr = (size_t)-1; 722 } 723 } 724 IsBigEndianMemoryEditor725 static bool IsBigEndian() 726 { 727 uint16_t x = 1; 728 char c[2]; 729 memcpy(c, &x, 2); 730 return c[0] != 0; 731 } 732 EndianessCopyBigEndianMemoryEditor733 static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian) 734 { 735 if (is_little_endian) 736 { 737 uint8_t* dst = (uint8_t*)_dst; 738 uint8_t* src = (uint8_t*)_src + s - 1; 739 for (int i = 0, n = (int)s; i < n; ++i) 740 memcpy(dst++, src--, 1); 741 return _dst; 742 } 743 else 744 { 745 return memcpy(_dst, _src, s); 746 } 747 } 748 EndianessCopyLittleEndianMemoryEditor749 static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian) 750 { 751 if (is_little_endian) 752 { 753 return memcpy(_dst, _src, s); 754 } 755 else 756 { 757 uint8_t* dst = (uint8_t*)_dst; 758 uint8_t* src = (uint8_t*)_src + s - 1; 759 for (int i = 0, n = (int)s; i < n; ++i) 760 memcpy(dst++, src--, 1); 761 return _dst; 762 } 763 } 764 EndianessCopyMemoryEditor765 void* EndianessCopy(void* dst, void* src, size_t size) const 766 { 767 static void* (*fp)(void*, void*, size_t, int) = NULL; 768 if (fp == NULL) 769 fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian; 770 return fp(dst, src, size, PreviewEndianess); 771 } 772 }; 773 774 #undef _PRISizeT 775 #undef ImSnprintf 776 777 #ifdef _MSC_VER 778 #pragma warning (pop) 779 #endif