1 // Mini memory editor for Dear ImGui (to embed in your game/tools) 2 // Animated GIF: https://twitter.com/ocornut/status/894242704317530112 3 // Get latest version at http://www.github.com/ocornut/imgui_club 4 // 5 // Right-click anywhere to access the Options menu! 6 // You can adjust the keyboard repeat delay/rate in ImGuiIO. 7 // The code assume a mono-space font for simplicity! 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 // static MemoryEditor mem_edit_1; // store your state somewhere 11 // mem_edit_1.DrawWindow("Memory Editor", mem_block, mem_block_size, 0x0000); // create a window and draw memory editor (if you already have a window, use DrawContents()) 12 // 13 // Usage: 14 // static MemoryEditor mem_edit_2; 15 // ImGui::Begin("MyWindow") 16 // mem_edit_2.DrawContents(this, sizeof(*this), (size_t)this); 17 // ImGui::End(); 18 // 19 // Changelog: 20 // - v0.10: initial version 21 // - v0.11: always refresh active text input with the latest byte from source memory if it's not being edited. 22 // - v0.12: added OptMidRowsCount to allow extra spacing every XX rows. 23 // - v0.13: added optional ReadFn/WriteFn handlers to access memory via a function. various warning fixes for 64-bits. 24 // - v0.14: added GotoAddr member, added GotoAddrAndHighlight() and highlighting. fixed minor scrollbar glitch when resizing. 25 // - v0.15: added maximum window width. minor optimization. 26 // - v0.16: added OptGreyOutZeroes option. various sizing fixes when resizing using the "Rows" drag. 27 // - v0.17: added HighlightFn handler for optional non-contiguous highlighting. 28 // - v0.18: fixes for displaying 64-bits addresses, fixed mouse click gaps introduced in recent changes, cursor tracking scrolling fixes. 29 // - v0.19: fixed auto-focus of next byte leaving WantCaptureKeyboard=false for one frame. we now capture the keyboard during that transition. 30 // - v0.20: added options menu. added OptShowAscii checkbox. added optional HexII display. split Draw() in DrawWindow()/DrawContents(). fixing glyph width. refactoring/cleaning code. 31 // - v0.21: fixes for using DrawContents() in our own window. fixed HexII to actually be useful and not on the wrong side. 32 // - v0.22: clicking Ascii view select the byte in the Hex view. Ascii view highlight selection. 33 // - v0.23: fixed right-arrow triggering a byte write. 34 // - v0.24: changed DragInt("Rows" to use a %d data format (which is desirable since imgui 1.61). 35 // - v0.25: fixed wording: all occurrences of "Rows" renamed to "Columns". 36 // - v0.26: fixed clicking on hex region 37 // - v0.30: added data preview for common data types 38 // - v0.31: added OptUpperCaseHex option to select lower/upper casing display [@samhocevar] 39 // - v0.32: changed signatures to use void* instead of unsigned char* 40 // - v0.33: added OptShowOptions option to hide all the interactive option setting. 41 // - v0.34: binary preview now applies endianess setting [@nicolasnoble] 42 // 43 // Todo/Bugs: 44 // - Arrows are being sent to the InputText() about to disappear which for LeftArrow makes the text cursor appear at position 1 for one frame. 45 // - Using InputText() is awkward and maybe overkill here, consider implementing something custom. 46 47 #pragma once 48 #include <stdio.h> // sprintf, scanf 49 #include <stdint.h> // uint8_t, etc. 50 51 #ifdef _MSC_VER 52 #define _PRISizeT "I" 53 #define ImSnprintf _snprintf 54 #else 55 #define _PRISizeT "z" 56 #define ImSnprintf snprintf 57 #endif 58 59 struct MemoryEditor 60 { 61 typedef unsigned char u8; 62 63 enum DataType 64 { 65 DataType_S8, 66 DataType_U8, 67 DataType_S16, 68 DataType_U16, 69 DataType_S32, 70 DataType_U32, 71 DataType_S64, 72 DataType_U64, 73 DataType_Float, 74 DataType_Double, 75 DataType_COUNT 76 }; 77 78 enum DataFormat 79 { 80 DataFormat_Bin = 0, 81 DataFormat_Dec = 1, 82 DataFormat_Hex = 2, 83 DataFormat_COUNT 84 }; 85 86 // Settings 87 bool Open; // = true // set to false when DrawWindow() was closed. ignore if not using DrawWindow(). 88 bool ReadOnly; // = false // disable any editing. 89 int Cols; // = 16 // number of columns to display. 90 bool OptShowOptions; // = true // display options button/context menu. when disabled, options will be locked unless you provide your own UI for them. 91 bool OptShowDataPreview; // = false // display a footer previewing the decimal/binary/hex/float representation of the currently selected bytes. 92 bool OptShowHexII; // = false // display values in HexII representation instead of regular hexadecimal: hide null/zero bytes, ascii values as ".X". 93 bool OptShowAscii; // = true // display ASCII representation on the right side. 94 bool OptGreyOutZeroes; // = true // display null/zero bytes using the TextDisabled color. 95 bool OptUpperCaseHex; // = true // display hexadecimal values as "FF" instead of "ff". 96 int OptMidColsCount; // = 8 // set to 0 to disable extra spacing between every mid-cols. 97 int OptAddrDigitsCount; // = 0 // number of addr digits to display (default calculated based on maximum displayed addr). 98 ImU32 HighlightColor; // // background color of highlighted bytes. 99 u8 (*ReadFn)(const u8* data, size_t off); // = NULL // optional handler to read bytes. 100 void (*WriteFn)(u8* data, size_t off, u8 d); // = NULL // optional handler to write bytes. 101 bool (*HighlightFn)(const u8* data, size_t off);//NULL // optional handler to return Highlight property (to support non-contiguous highlighting). 102 103 // [Internal State] 104 bool ContentsWidthChanged; 105 size_t DataPreviewAddr; 106 size_t DataEditingAddr; 107 bool DataEditingTakeFocus; 108 char DataInputBuf[32]; 109 char AddrInputBuf[32]; 110 size_t GotoAddr; 111 size_t HighlightMin, HighlightMax; 112 int PreviewEndianess; 113 DataType PreviewDataType; 114 MemoryEditorMemoryEditor115 MemoryEditor() 116 { 117 // Settings 118 Open = true; 119 ReadOnly = false; 120 Cols = 16; 121 OptShowOptions = true; 122 OptShowDataPreview = false; 123 OptShowHexII = false; 124 OptShowAscii = true; 125 OptGreyOutZeroes = true; 126 OptUpperCaseHex = true; 127 OptMidColsCount = 8; 128 OptAddrDigitsCount = 0; 129 HighlightColor = IM_COL32(255, 255, 255, 50); 130 ReadFn = NULL; 131 WriteFn = NULL; 132 HighlightFn = NULL; 133 134 // State/Internals 135 ContentsWidthChanged = false; 136 DataPreviewAddr = DataEditingAddr = (size_t)-1; 137 DataEditingTakeFocus = false; 138 memset(DataInputBuf, 0, sizeof(DataInputBuf)); 139 memset(AddrInputBuf, 0, sizeof(AddrInputBuf)); 140 GotoAddr = (size_t)-1; 141 HighlightMin = HighlightMax = (size_t)-1; 142 PreviewEndianess = 0; 143 PreviewDataType = DataType_S32; 144 } 145 GotoAddrAndHighlightMemoryEditor146 void GotoAddrAndHighlight(size_t addr_min, size_t addr_max) 147 { 148 GotoAddr = addr_min; 149 HighlightMin = addr_min; 150 HighlightMax = addr_max; 151 } 152 153 struct Sizes 154 { 155 int AddrDigitsCount; 156 float LineHeight; 157 float GlyphWidth; 158 float HexCellWidth; 159 float SpacingBetweenMidCols; 160 float PosHexStart; 161 float PosHexEnd; 162 float PosAsciiStart; 163 float PosAsciiEnd; 164 float WindowWidth; 165 }; 166 CalcSizesMemoryEditor167 void CalcSizes(Sizes& s, size_t mem_size, size_t base_display_addr) 168 { 169 ImGuiStyle& style = ImGui::GetStyle(); 170 s.AddrDigitsCount = OptAddrDigitsCount; 171 if (s.AddrDigitsCount == 0) 172 for (size_t n = base_display_addr + mem_size - 1; n > 0; n >>= 4) 173 s.AddrDigitsCount++; 174 s.LineHeight = ImGui::GetTextLineHeight(); 175 s.GlyphWidth = ImGui::CalcTextSize("F").x + 1; // We assume the font is mono-space 176 s.HexCellWidth = (float)(int)(s.GlyphWidth * 2.5f); // "FF " we include trailing space in the width to easily catch clicks everywhere 177 s.SpacingBetweenMidCols = (float)(int)(s.HexCellWidth * 0.25f); // Every OptMidColsCount columns we add a bit of extra spacing 178 s.PosHexStart = (s.AddrDigitsCount + 2) * s.GlyphWidth; 179 s.PosHexEnd = s.PosHexStart + (s.HexCellWidth * Cols); 180 s.PosAsciiStart = s.PosAsciiEnd = s.PosHexEnd; 181 if (OptShowAscii) 182 { 183 s.PosAsciiStart = s.PosHexEnd + s.GlyphWidth * 1; 184 if (OptMidColsCount > 0) 185 s.PosAsciiStart += (float)((Cols + OptMidColsCount - 1) / OptMidColsCount) * s.SpacingBetweenMidCols; 186 s.PosAsciiEnd = s.PosAsciiStart + Cols * s.GlyphWidth; 187 } 188 s.WindowWidth = s.PosAsciiEnd + style.ScrollbarSize + style.WindowPadding.x * 2 + s.GlyphWidth; 189 } 190 191 // Standalone Memory Editor window 192 void DrawWindow(const char* title, void* mem_data, size_t mem_size, size_t base_display_addr = 0x0000) 193 { 194 Sizes s; 195 CalcSizes(s, mem_size, base_display_addr); 196 ImGui::SetNextWindowSizeConstraints(ImVec2(0.0f, 0.0f), ImVec2(s.WindowWidth, FLT_MAX)); 197 198 Open = true; 199 if (ImGui::Begin(title, &Open, ImGuiWindowFlags_NoScrollbar)) 200 { 201 if (ImGui::IsWindowHovered(ImGuiHoveredFlags_RootAndChildWindows) && ImGui::IsMouseClicked(1)) 202 ImGui::OpenPopup("context"); 203 DrawContents(mem_data, mem_size, base_display_addr); 204 if (ContentsWidthChanged) 205 { 206 CalcSizes(s, mem_size, base_display_addr); 207 ImGui::SetWindowSize(ImVec2(s.WindowWidth, ImGui::GetWindowSize().y)); 208 } 209 } 210 ImGui::End(); 211 } 212 213 // Memory Editor contents only 214 void DrawContents(void* mem_data_void_ptr, size_t mem_size, size_t base_display_addr = 0x0000) 215 { 216 u8* mem_data = (u8*)mem_data_void_ptr; 217 Sizes s; 218 CalcSizes(s, mem_size, base_display_addr); 219 ImGuiStyle& style = ImGui::GetStyle(); 220 221 // We begin into our scrolling region with the 'ImGuiWindowFlags_NoMove' in order to prevent click from moving the window. 222 // 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. 223 const float height_separator = style.ItemSpacing.y; 224 float footer_height = 0; 225 if (OptShowOptions) 226 footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1; 227 if (OptShowDataPreview) 228 footer_height += height_separator + ImGui::GetFrameHeightWithSpacing() * 1 + ImGui::GetTextLineHeightWithSpacing() * 3; 229 ImGui::BeginChild("##scrolling", ImVec2(0, -footer_height), false, ImGuiWindowFlags_NoMove); 230 ImDrawList* draw_list = ImGui::GetWindowDrawList(); 231 232 ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0)); 233 ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0)); 234 235 const int line_total_count = (int)((mem_size + Cols - 1) / Cols); 236 ImGuiListClipper clipper(line_total_count, s.LineHeight); 237 const size_t visible_start_addr = clipper.DisplayStart * Cols; 238 const size_t visible_end_addr = clipper.DisplayEnd * Cols; 239 240 bool data_next = false; 241 242 if (ReadOnly || DataEditingAddr >= mem_size) 243 DataEditingAddr = (size_t)-1; 244 if (DataPreviewAddr >= mem_size) 245 DataPreviewAddr = (size_t)-1; 246 247 size_t preview_data_type_size = OptShowDataPreview ? DataTypeGetSize(PreviewDataType) : 0; 248 249 size_t data_editing_addr_backup = DataEditingAddr; 250 size_t data_editing_addr_next = (size_t)-1; 251 if (DataEditingAddr != (size_t)-1) 252 { 253 // 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) 254 if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_UpArrow)) && DataEditingAddr >= (size_t)Cols) { data_editing_addr_next = DataEditingAddr - Cols; DataEditingTakeFocus = true; } 255 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_DownArrow)) && DataEditingAddr < mem_size - Cols) { data_editing_addr_next = DataEditingAddr + Cols; DataEditingTakeFocus = true; } 256 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_LeftArrow)) && DataEditingAddr > 0) { data_editing_addr_next = DataEditingAddr - 1; DataEditingTakeFocus = true; } 257 else if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(ImGuiKey_RightArrow)) && DataEditingAddr < mem_size - 1) { data_editing_addr_next = DataEditingAddr + 1; DataEditingTakeFocus = true; } 258 } 259 if (data_editing_addr_next != (size_t)-1 && (data_editing_addr_next / Cols) != (data_editing_addr_backup / Cols)) 260 { 261 // Track cursor movements 262 const int scroll_offset = ((int)(data_editing_addr_next / Cols) - (int)(data_editing_addr_backup / Cols)); 263 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); 264 if (scroll_desired) 265 ImGui::SetScrollY(ImGui::GetScrollY() + scroll_offset * s.LineHeight); 266 } 267 268 // Draw vertical separator 269 ImVec2 window_pos = ImGui::GetWindowPos(); 270 if (OptShowAscii) 271 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)); 272 273 const ImU32 color_text = ImGui::GetColorU32(ImGuiCol_Text); 274 const ImU32 color_disabled = OptGreyOutZeroes ? ImGui::GetColorU32(ImGuiCol_TextDisabled) : color_text; 275 276 const char* format_address = OptUpperCaseHex ? "%0*" _PRISizeT "X: " : "%0*" _PRISizeT "x: "; 277 const char* format_data = OptUpperCaseHex ? "%0*" _PRISizeT "X" : "%0*" _PRISizeT "x"; 278 const char* format_range = OptUpperCaseHex ? "Range %0*" _PRISizeT "X..%0*" _PRISizeT "X" : "Range %0*" _PRISizeT "x..%0*" _PRISizeT "x"; 279 const char* format_byte = OptUpperCaseHex ? "%02X" : "%02x"; 280 const char* format_byte_space = OptUpperCaseHex ? "%02X " : "%02x "; 281 282 for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) // display only visible lines 283 { 284 size_t addr = (size_t)(line_i * Cols); 285 ImGui::Text(format_address, s.AddrDigitsCount, base_display_addr + addr); 286 287 // Draw Hexadecimal 288 for (int n = 0; n < Cols && addr < mem_size; n++, addr++) 289 { 290 float byte_pos_x = s.PosHexStart + s.HexCellWidth * n; 291 if (OptMidColsCount > 0) 292 byte_pos_x += (float)(n / OptMidColsCount) * s.SpacingBetweenMidCols; 293 ImGui::SameLine(byte_pos_x); 294 295 // Draw highlight 296 bool is_highlight_from_user_range = (addr >= HighlightMin && addr < HighlightMax); 297 bool is_highlight_from_user_func = (HighlightFn && HighlightFn(mem_data, addr)); 298 bool is_highlight_from_preview = (addr >= DataPreviewAddr && addr < DataPreviewAddr + preview_data_type_size); 299 if (is_highlight_from_user_range || is_highlight_from_user_func || is_highlight_from_preview) 300 { 301 ImVec2 pos = ImGui::GetCursorScreenPos(); 302 float highlight_width = s.GlyphWidth * 2; 303 bool is_next_byte_highlighted = (addr + 1 < mem_size) && ((HighlightMax != (size_t)-1 && addr + 1 < HighlightMax) || (HighlightFn && HighlightFn(mem_data, addr + 1))); 304 if (is_next_byte_highlighted || (n + 1 == Cols)) 305 { 306 highlight_width = s.HexCellWidth; 307 if (OptMidColsCount > 0 && n > 0 && (n + 1) < Cols && ((n + 1) % OptMidColsCount) == 0) 308 highlight_width += s.SpacingBetweenMidCols; 309 } 310 draw_list->AddRectFilled(pos, ImVec2(pos.x + highlight_width, pos.y + s.LineHeight), HighlightColor); 311 } 312 313 if (DataEditingAddr == addr) 314 { 315 // Display text input on current byte 316 bool data_write = false; 317 ImGui::PushID((void*)addr); 318 if (DataEditingTakeFocus) 319 { 320 ImGui::SetKeyboardFocusHere(); 321 ImGui::CaptureKeyboardFromApp(true); 322 sprintf(AddrInputBuf, format_data, s.AddrDigitsCount, base_display_addr + addr); 323 sprintf(DataInputBuf, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); 324 } 325 ImGui::PushItemWidth(s.GlyphWidth * 2); 326 struct UserData 327 { 328 // 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::UserData329 static int Callback(ImGuiInputTextCallbackData* data) 330 { 331 UserData* user_data = (UserData*)data->UserData; 332 if (!data->HasSelection()) 333 user_data->CursorPos = data->CursorPos; 334 if (data->SelectionStart == 0 && data->SelectionEnd == data->BufTextLen) 335 { 336 // 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) 337 data->DeleteChars(0, data->BufTextLen); 338 data->InsertChars(0, user_data->CurrentBufOverwrite); 339 data->SelectionStart = 0; 340 data->SelectionEnd = data->CursorPos = 2; 341 } 342 return 0; 343 } 344 char CurrentBufOverwrite[3]; // Input 345 int CursorPos; // Output 346 }; 347 UserData user_data; 348 user_data.CursorPos = -1; 349 sprintf(user_data.CurrentBufOverwrite, format_byte, ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]); 350 ImGuiInputTextFlags flags = ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue | ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_NoHorizontalScroll | ImGuiInputTextFlags_AlwaysInsertMode | ImGuiInputTextFlags_CallbackAlways; 351 if (ImGui::InputText("##data", DataInputBuf, 32, flags, UserData::Callback, &user_data)) 352 data_write = data_next = true; 353 else if (!DataEditingTakeFocus && !ImGui::IsItemActive()) 354 DataEditingAddr = data_editing_addr_next = (size_t)-1; 355 DataEditingTakeFocus = false; 356 ImGui::PopItemWidth(); 357 if (user_data.CursorPos >= 2) 358 data_write = data_next = true; 359 if (data_editing_addr_next != (size_t)-1) 360 data_write = data_next = false; 361 unsigned int data_input_value = 0; 362 if (data_write && sscanf(DataInputBuf, "%X", &data_input_value) == 1) 363 { 364 if (WriteFn) 365 WriteFn(mem_data, addr, (u8)data_input_value); 366 else 367 mem_data[addr] = (u8)data_input_value; 368 } 369 ImGui::PopID(); 370 } 371 else 372 { 373 // NB: The trailing space is not visible but ensure there's no gap that the mouse cannot click on. 374 u8 b = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; 375 376 if (OptShowHexII) 377 { 378 if ((b >= 32 && b < 128)) 379 ImGui::Text(".%c ", b); 380 else if (b == 0xFF && OptGreyOutZeroes) 381 ImGui::TextDisabled("## "); 382 else if (b == 0x00) 383 ImGui::Text(" "); 384 else 385 ImGui::Text(format_byte_space, b); 386 } 387 else 388 { 389 if (b == 0 && OptGreyOutZeroes) 390 ImGui::TextDisabled("00 "); 391 else 392 ImGui::Text(format_byte_space, b); 393 } 394 if (!ReadOnly && ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) 395 { 396 DataEditingTakeFocus = true; 397 data_editing_addr_next = addr; 398 } 399 } 400 } 401 402 if (OptShowAscii) 403 { 404 // Draw ASCII values 405 ImGui::SameLine(s.PosAsciiStart); 406 ImVec2 pos = ImGui::GetCursorScreenPos(); 407 addr = line_i * Cols; 408 ImGui::PushID(line_i); 409 if (ImGui::InvisibleButton("ascii", ImVec2(s.PosAsciiEnd - s.PosAsciiStart, s.LineHeight))) 410 { 411 DataEditingAddr = DataPreviewAddr = addr + (size_t)((ImGui::GetIO().MousePos.x - pos.x) / s.GlyphWidth); 412 DataEditingTakeFocus = true; 413 } 414 ImGui::PopID(); 415 for (int n = 0; n < Cols && addr < mem_size; n++, addr++) 416 { 417 if (addr == DataEditingAddr) 418 { 419 draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_FrameBg)); 420 draw_list->AddRectFilled(pos, ImVec2(pos.x + s.GlyphWidth, pos.y + s.LineHeight), ImGui::GetColorU32(ImGuiCol_TextSelectedBg)); 421 } 422 unsigned char c = ReadFn ? ReadFn(mem_data, addr) : mem_data[addr]; 423 char display_c = (c < 32 || c >= 128) ? '.' : c; 424 draw_list->AddText(pos, (display_c == '.') ? color_disabled : color_text, &display_c, &display_c + 1); 425 pos.x += s.GlyphWidth; 426 } 427 } 428 } 429 clipper.End(); 430 ImGui::PopStyleVar(2); 431 ImGui::EndChild(); 432 433 if (data_next && DataEditingAddr < mem_size) 434 { 435 DataEditingAddr = DataPreviewAddr = DataEditingAddr + 1; 436 DataEditingTakeFocus = true; 437 } 438 else if (data_editing_addr_next != (size_t)-1) 439 { 440 DataEditingAddr = DataPreviewAddr = data_editing_addr_next; 441 } 442 443 bool next_show_data_preview = OptShowDataPreview; 444 if (OptShowOptions) 445 { 446 ImGui::Separator(); 447 448 // Options menu 449 450 if (ImGui::Button("Options")) 451 ImGui::OpenPopup("context"); 452 if (ImGui::BeginPopup("context")) 453 { 454 ImGui::PushItemWidth(56); 455 if (ImGui::DragInt("##cols", &Cols, 0.2f, 4, 32, "%d cols")) { ContentsWidthChanged = true; } 456 ImGui::PopItemWidth(); 457 ImGui::Checkbox("Show Data Preview", &next_show_data_preview); 458 ImGui::Checkbox("Show HexII", &OptShowHexII); 459 if (ImGui::Checkbox("Show Ascii", &OptShowAscii)) { ContentsWidthChanged = true; } 460 ImGui::Checkbox("Grey out zeroes", &OptGreyOutZeroes); 461 ImGui::Checkbox("Uppercase Hex", &OptUpperCaseHex); 462 463 ImGui::EndPopup(); 464 } 465 466 ImGui::SameLine(); 467 ImGui::Text(format_range, s.AddrDigitsCount, base_display_addr, s.AddrDigitsCount, base_display_addr + mem_size - 1); 468 ImGui::SameLine(); 469 ImGui::PushItemWidth((s.AddrDigitsCount + 1) * s.GlyphWidth + style.FramePadding.x * 2.0f); 470 if (ImGui::InputText("##addr", AddrInputBuf, 32, ImGuiInputTextFlags_CharsHexadecimal | ImGuiInputTextFlags_EnterReturnsTrue)) 471 { 472 size_t goto_addr; 473 if (sscanf(AddrInputBuf, "%" _PRISizeT "X", &goto_addr) == 1) 474 { 475 GotoAddr = goto_addr - base_display_addr; 476 HighlightMin = HighlightMax = (size_t)-1; 477 } 478 } 479 ImGui::PopItemWidth(); 480 481 if (GotoAddr != (size_t)-1) 482 { 483 if (GotoAddr < mem_size) 484 { 485 ImGui::BeginChild("##scrolling"); 486 ImGui::SetScrollFromPosY(ImGui::GetCursorStartPos().y + (GotoAddr / Cols) * ImGui::GetTextLineHeight()); 487 ImGui::EndChild(); 488 DataEditingAddr = DataPreviewAddr = GotoAddr; 489 DataEditingTakeFocus = true; 490 } 491 GotoAddr = (size_t)-1; 492 } 493 } 494 495 if (OptShowDataPreview) 496 { 497 ImGui::Separator(); 498 ImGui::AlignTextToFramePadding(); 499 ImGui::Text("Preview as:"); 500 ImGui::SameLine(); 501 ImGui::PushItemWidth((s.GlyphWidth * 10.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); 502 if (ImGui::BeginCombo("##combo_type", DataTypeGetDesc(PreviewDataType), ImGuiComboFlags_HeightLargest)) 503 { 504 for (int n = 0; n < DataType_COUNT; n++) 505 if (ImGui::Selectable(DataTypeGetDesc((DataType)n), PreviewDataType == n)) 506 PreviewDataType = (DataType)n; 507 ImGui::EndCombo(); 508 } 509 ImGui::PopItemWidth(); 510 ImGui::SameLine(); 511 ImGui::PushItemWidth((s.GlyphWidth * 6.0f) + style.FramePadding.x * 2.0f + style.ItemInnerSpacing.x); 512 ImGui::Combo("##combo_endianess", &PreviewEndianess, "LE\0BE\0\0"); 513 ImGui::PopItemWidth(); 514 515 char buf[128]; 516 float x = s.GlyphWidth * 6.0f; 517 bool has_value = DataPreviewAddr != (size_t)-1; 518 if (has_value) 519 DisplayPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Dec, buf, (size_t)IM_ARRAYSIZE(buf)); 520 ImGui::Text("Dec"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); 521 if (has_value) 522 DisplayPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Hex, buf, (size_t)IM_ARRAYSIZE(buf)); 523 ImGui::Text("Hex"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); 524 if (has_value) 525 DisplayPreviewData(DataPreviewAddr, mem_data, mem_size, PreviewDataType, DataFormat_Bin, buf, (size_t)IM_ARRAYSIZE(buf)); 526 ImGui::Text("Bin"); ImGui::SameLine(x); ImGui::TextUnformatted(has_value ? buf : "N/A"); 527 } 528 529 OptShowDataPreview = next_show_data_preview; 530 531 // Notify the main window of our ideal child content size (FIXME: we are missing an API to get the contents size from the child) 532 ImGui::SetCursorPosX(s.WindowWidth); 533 } 534 535 // Utilities for Data Preview DataTypeGetDescMemoryEditor536 const char* DataTypeGetDesc(DataType data_type) const 537 { 538 const char* descs[] = { "Int8", "Uint8", "Int16", "Uint16", "Int32", "Uint32", "Int64", "Uint64", "Float", "Double" }; 539 IM_ASSERT(data_type >= 0 && data_type < DataType_COUNT); 540 return descs[data_type]; 541 } 542 DataTypeGetSizeMemoryEditor543 size_t DataTypeGetSize(DataType data_type) const 544 { 545 const size_t sizes[] = { 1, 1, 2, 2, 4, 4, 8, 8, 4, 8 }; 546 IM_ASSERT(data_type >= 0 && data_type < DataType_COUNT); 547 return sizes[data_type]; 548 } 549 DataFormatGetDescMemoryEditor550 const char* DataFormatGetDesc(DataFormat data_format) const 551 { 552 const char* descs[] = { "Bin", "Dec", "Hex" }; 553 IM_ASSERT(data_format >= 0 && data_format < DataFormat_COUNT); 554 return descs[data_format]; 555 } 556 IsBigEndianMemoryEditor557 bool IsBigEndian() const 558 { 559 uint16_t x = 1; 560 char c[2]; 561 memcpy(c, &x, 2); 562 return c[0] != 0; 563 } 564 EndianessCopyBigEndianMemoryEditor565 static void* EndianessCopyBigEndian(void* _dst, void* _src, size_t s, int is_little_endian) 566 { 567 if (is_little_endian) 568 { 569 uint8_t* dst = (uint8_t*)_dst; 570 uint8_t* src = (uint8_t*)_src + s - 1; 571 for (int i = 0, n = (int)s; i < n; ++i) 572 memcpy(dst++, src--, 1); 573 return _dst; 574 } 575 else 576 { 577 return memcpy(_dst, _src, s); 578 } 579 } 580 EndianessCopyLittleEndianMemoryEditor581 static void* EndianessCopyLittleEndian(void* _dst, void* _src, size_t s, int is_little_endian) 582 { 583 if (is_little_endian) 584 { 585 return memcpy(_dst, _src, s); 586 } 587 else 588 { 589 uint8_t* dst = (uint8_t*)_dst; 590 uint8_t* src = (uint8_t*)_src + s - 1; 591 for (int i = 0, n = (int)s; i < n; ++i) 592 memcpy(dst++, src--, 1); 593 return _dst; 594 } 595 } 596 EndianessCopyMemoryEditor597 void* EndianessCopy(void *dst, void *src, size_t size) const 598 { 599 static void *(*fp)(void *, void *, size_t, int) = NULL; 600 if (fp == NULL) 601 fp = IsBigEndian() ? EndianessCopyBigEndian : EndianessCopyLittleEndian; 602 return fp(dst, src, size, PreviewEndianess); 603 } 604 FormatBinaryMemoryEditor605 const char* FormatBinary(const uint8_t* buf, int width) const 606 { 607 IM_ASSERT(width <= 64); 608 size_t out_n = 0; 609 static char out_buf[64 + 8 + 1]; 610 int n = width / 8; 611 for (int j = n - 1; j >= 0; --j) 612 { 613 for (int i = 0; i < 8; ++i) 614 out_buf[out_n++] = (buf[j] & (1 << (7 - i))) ? '1' : '0'; 615 out_buf[out_n++] = ' '; 616 } 617 IM_ASSERT(out_n < IM_ARRAYSIZE(out_buf)); 618 out_buf[out_n] = 0; 619 return out_buf; 620 } 621 DisplayPreviewDataMemoryEditor622 void DisplayPreviewData(size_t addr, const u8* mem_data, size_t mem_size, DataType data_type, DataFormat data_format, char* out_buf, size_t out_buf_size) const 623 { 624 uint8_t buf[8]; 625 size_t elem_size = DataTypeGetSize(data_type); 626 size_t size = addr + elem_size > mem_size ? mem_size - addr : elem_size; 627 if (ReadFn) 628 for (int i = 0, n = (int)size; i < n; ++i) 629 buf[i] = ReadFn(mem_data, addr + i); 630 else 631 memcpy(buf, mem_data + addr, size); 632 633 if (data_format == DataFormat_Bin) 634 { 635 uint8_t binbuf[8]; 636 EndianessCopy(binbuf, buf, size); 637 ImSnprintf(out_buf, out_buf_size, "%s", FormatBinary(binbuf, (int)size * 8)); 638 return; 639 } 640 641 out_buf[0] = 0; 642 switch (data_type) 643 { 644 case DataType_S8: 645 { 646 int8_t int8 = 0; 647 EndianessCopy(&int8, buf, size); 648 if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhd", int8); return; } 649 if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", int8 & 0xFF); return; } 650 break; 651 } 652 case DataType_U8: 653 { 654 uint8_t uint8 = 0; 655 EndianessCopy(&uint8, buf, size); 656 if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hhu", uint8); return; } 657 if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%02x", uint8 & 0XFF); return; } 658 break; 659 } 660 case DataType_S16: 661 { 662 int16_t int16 = 0; 663 EndianessCopy(&int16, buf, size); 664 if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hd", int16); return; } 665 if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", int16 & 0xFFFF); return; } 666 break; 667 } 668 case DataType_U16: 669 { 670 uint16_t uint16 = 0; 671 EndianessCopy(&uint16, buf, size); 672 if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%hu", uint16); return; } 673 if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%04x", uint16 & 0xFFFF); return; } 674 break; 675 } 676 case DataType_S32: 677 { 678 int32_t int32 = 0; 679 EndianessCopy(&int32, buf, size); 680 if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%d", int32); return; } 681 if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", int32); return; } 682 break; 683 } 684 case DataType_U32: 685 { 686 uint32_t uint32 = 0; 687 EndianessCopy(&uint32, buf, size); 688 if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%u", uint32); return; } 689 if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%08x", uint32); return; } 690 break; 691 } 692 case DataType_S64: 693 { 694 int64_t int64 = 0; 695 EndianessCopy(&int64, buf, size); 696 if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%lld", (long long)int64); return; } 697 if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)int64); return; } 698 break; 699 } 700 case DataType_U64: 701 { 702 uint64_t uint64 = 0; 703 EndianessCopy(&uint64, buf, size); 704 if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%llu", (long long)uint64); return; } 705 if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "0x%016llx", (long long)uint64); return; } 706 break; 707 } 708 case DataType_Float: 709 { 710 float float32 = 0.0f; 711 EndianessCopy(&float32, buf, size); 712 if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float32); return; } 713 if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float32); return; } 714 break; 715 } 716 case DataType_Double: 717 { 718 double float64 = 0.0; 719 EndianessCopy(&float64, buf, size); 720 if (data_format == DataFormat_Dec) { ImSnprintf(out_buf, out_buf_size, "%f", float64); return; } 721 if (data_format == DataFormat_Hex) { ImSnprintf(out_buf, out_buf_size, "%a", float64); return; } 722 break; 723 } 724 case DataType_COUNT: 725 break; 726 } // Switch 727 IM_ASSERT(0); // Shouldn't reach 728 } 729 }; 730 731 #undef _PRISizeT 732 #undef ImSnprintf 733