Created
January 29, 2025 15:56
-
-
Save japajoe/558a86396d418a5bab8e63fee11fcc4f to your computer and use it in GitHub Desktop.
ImGui MemoryViewer in C#
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Runtime.InteropServices; | |
| using ImGuiNET; | |
| using System.Numerics; | |
| public sealed unsafe class MemoryViewer | |
| { | |
| public bool Open = true; | |
| public bool ReadOnly = false; | |
| public int Cols = 16; | |
| public bool OptShowOptions = true; | |
| public bool OptShowDataPreview = true; | |
| public bool OptShowHexII = false; | |
| public bool OptShowAscii = true; | |
| public bool OptGreyOutZeroes = true; | |
| public bool OptUpperCaseHex = true; | |
| public int OptMidColsCount = 8; | |
| public int OptAddrDigitsCount = 0; | |
| public float OptFooterExtraHeight = 0.0f; | |
| public uint HighlightColor = ImGui.ColorConvertFloat4ToU32(new Vector4(1.0f, 1.0f, 1.0f, 0.2f)); | |
| public delegate byte ReadFnDelegate(IntPtr mem, ulong off, IntPtr userData); | |
| public delegate void WriteFnDelegate(IntPtr mem, ulong off, byte d, IntPtr userData); | |
| public delegate bool HighlightFnDelegate(IntPtr mem, ulong off, IntPtr userData); | |
| public delegate uint BgColorFnDelegate(IntPtr mem, ulong off, IntPtr userData); | |
| public ReadFnDelegate ReadFn = null; | |
| public WriteFnDelegate WriteFn = null; | |
| public HighlightFnDelegate HighlightFn = null; | |
| public BgColorFnDelegate BgColorFn = null; | |
| public IntPtr UserData = IntPtr.Zero; | |
| public bool MouseHovered = false; | |
| public ulong MouseHoveredAddr = 0; | |
| private bool _contentsWidthChanged = false; | |
| private ulong _dataPreviewAddr = ulong.MaxValue; | |
| private ulong _dataEditingAddr = ulong.MaxValue; | |
| private bool _dataEditingTakeFocus = false; | |
| private byte[] _dataInputBuf = new byte[32]; | |
| private byte[] _addrInputBuf = new byte[32]; | |
| private ulong _gotoAddr = ulong.MaxValue; | |
| private ulong _highlightMin = ulong.MaxValue; | |
| private ulong _highlightMax = ulong.MaxValue; | |
| private int _previewEndianness = 0; | |
| private ImGuiDataType _previewDataType = ImGuiDataType.S32; | |
| public void DrawWindow(string title, IntPtr memData, ulong memSize, ulong baseDisplayAddr = 0) | |
| { | |
| if (!Open) return; | |
| ImGui.SetNextWindowSize(new Vector2(500, 300), ImGuiCond.FirstUseEver); | |
| if (ImGui.Begin(title, ref Open)) | |
| { | |
| DrawContents(memData, memSize, baseDisplayAddr); | |
| } | |
| ImGui.End(); | |
| } | |
| private void DrawContents(IntPtr memData, ulong memSize, ulong baseDisplayAddr = 0) | |
| { | |
| if (Cols < 1) Cols = 1; | |
| Sizes sizes = new Sizes(); | |
| CalcSizes(sizes, memSize, baseDisplayAddr); | |
| if (OptShowOptions) | |
| { | |
| DrawOptionsLine(sizes, memData, memSize, baseDisplayAddr); | |
| ImGui.Separator(); | |
| } | |
| if (OptShowDataPreview) | |
| { | |
| //ImGui.Separator(); | |
| DrawPreviewLine(sizes, memData, memSize, baseDisplayAddr); | |
| } | |
| ImGui.BeginChild("##scrolling", new Vector2(0, -ImGui.GetFrameHeightWithSpacing() - OptFooterExtraHeight), ImGuiChildFlags.None, ImGuiWindowFlags.NoMove); | |
| ImGui.PushStyleVar(ImGuiStyleVar.FramePadding, new Vector2(0, 0)); | |
| ImGui.PushStyleVar(ImGuiStyleVar.ItemSpacing, new Vector2(0, 0)); | |
| int lineTotalCount = (int)(((int)memSize + Cols - 1) / Cols); | |
| ImGuiListClipperEx clipper = new ImGuiListClipperEx(); | |
| clipper.Begin(lineTotalCount, sizes.LineHeight); | |
| while (clipper.Step()) | |
| { | |
| for (int line_i = clipper.DisplayStart; line_i < clipper.DisplayEnd; line_i++) | |
| { | |
| ulong addr = (ulong)line_i * (ulong)Cols; | |
| ImGui.Text($"{baseDisplayAddr + addr:X8}: "); | |
| for (int n = 0; n < Cols && addr < memSize; n++, addr++) | |
| { | |
| float bytePosX = sizes.PosHexStart + sizes.HexCellWidth * n; | |
| if (OptMidColsCount > 0) | |
| bytePosX += (n / OptMidColsCount) * sizes.SpacingBetweenMidCols; | |
| ImGui.SameLine(bytePosX); | |
| byte b = ReadFn != null ? ReadFn(memData, addr, UserData) : Marshal.ReadByte(memData + (int)addr); | |
| if (OptShowHexII) | |
| { | |
| if (b >= 32 && b < 128) | |
| ImGui.Text($".{(char)b} "); | |
| else if (b == 0xFF && OptGreyOutZeroes) | |
| ImGui.TextDisabled("## "); | |
| else if (b == 0x00) | |
| ImGui.Text(" "); | |
| else | |
| ImGui.Text($"{b:X2} "); | |
| } | |
| else | |
| { | |
| if (b == 0 && OptGreyOutZeroes) | |
| ImGui.TextDisabled("00 "); | |
| else | |
| ImGui.Text($"{b:X2} "); | |
| } | |
| if (ImGui.IsItemHovered()) | |
| { | |
| MouseHovered = true; | |
| MouseHoveredAddr = addr; | |
| } | |
| } | |
| if (OptShowAscii) | |
| { | |
| //ImGui.SameLine(sizes.PosAsciiStart); | |
| addr = (ulong)line_i * (ulong)Cols; | |
| for (int n = 0; n < Cols && addr < memSize; n++, addr++) | |
| { | |
| ImGui.SameLine(); | |
| byte b = ReadFn != null ? ReadFn(memData, addr, UserData) : Marshal.ReadByte(memData + (int)addr); | |
| char c = (b < 32 || b >= 128) ? '.' : (char)b; | |
| ImGui.Text($"{c}"); | |
| //ImGui.SameLine(); | |
| } | |
| } | |
| } | |
| } | |
| ImGui.PopStyleVar(2); | |
| ImGui.EndChild(); | |
| clipper.Dispose(); | |
| } | |
| private void CalcSizes(Sizes sizes, ulong memSize, ulong baseDisplayAddr) | |
| { | |
| sizes.AddrDigitsCount = OptAddrDigitsCount; | |
| if (sizes.AddrDigitsCount == 0) | |
| { | |
| for (ulong n = baseDisplayAddr + memSize - 1; n > 0; n >>= 4) | |
| sizes.AddrDigitsCount++; | |
| } | |
| sizes.LineHeight = ImGui.GetTextLineHeight(); | |
| sizes.GlyphWidth = ImGui.CalcTextSize("F").X + 1; | |
| sizes.HexCellWidth = (float)(int)(sizes.GlyphWidth * 2.5f); | |
| sizes.SpacingBetweenMidCols = (float)(int)(sizes.HexCellWidth * 0.25f); | |
| sizes.PosHexStart = (sizes.AddrDigitsCount + 2) * sizes.GlyphWidth; | |
| sizes.PosHexEnd = sizes.PosHexStart + (sizes.HexCellWidth * Cols); | |
| sizes.PosAsciiStart = sizes.PosHexEnd + sizes.GlyphWidth * 1; | |
| sizes.PosAsciiEnd = sizes.PosAsciiStart + Cols * sizes.GlyphWidth; | |
| sizes.WindowWidth = sizes.PosAsciiEnd + ImGui.GetStyle().ScrollbarSize + ImGui.GetStyle().WindowPadding.X * 2 + sizes.GlyphWidth; | |
| } | |
| private void DrawOptionsLine(Sizes sizes, IntPtr memData, ulong memSize, ulong baseDisplayAddr) | |
| { | |
| ImGui.Text($"Range {baseDisplayAddr:X8}..{baseDisplayAddr + memSize - 1:X8}"); | |
| ImGui.SameLine(); | |
| ImGui.SetNextItemWidth(128); | |
| if (ImGui.InputText("##addr", _addrInputBuf, (uint)_addrInputBuf.Length, ImGuiInputTextFlags.EnterReturnsTrue)) | |
| { | |
| string address = System.Text.Encoding.ASCII.GetString(_addrInputBuf); | |
| address = address.ToLower().Trim(); | |
| if(address.StartsWith("0x")) | |
| { | |
| address = address.Replace("0x", string.Empty); | |
| if (ulong.TryParse(address, System.Globalization.NumberStyles.HexNumber, null, out ulong gotoAddr)) | |
| { | |
| _gotoAddr = gotoAddr - baseDisplayAddr; | |
| _highlightMin = _highlightMax = ulong.MaxValue; | |
| } | |
| } | |
| else | |
| { | |
| if (ulong.TryParse(address, out ulong gotoAddr)) | |
| { | |
| _gotoAddr = gotoAddr - baseDisplayAddr; | |
| _highlightMin = _highlightMax = ulong.MaxValue; | |
| } | |
| } | |
| } | |
| if (_gotoAddr != ulong.MaxValue) | |
| { | |
| if (_gotoAddr < memSize) | |
| { | |
| ImGui.BeginChild("##scrolling"); | |
| ImGui.SetScrollFromPosY(ImGui.GetCursorStartPos().Y + (_gotoAddr / (ulong)Cols) * ImGui.GetTextLineHeight()); | |
| ImGui.EndChild(); | |
| _dataEditingAddr = _dataPreviewAddr = _gotoAddr; | |
| _dataEditingTakeFocus = true; | |
| } | |
| _gotoAddr = ulong.MaxValue; | |
| } | |
| } | |
| private void DrawPreviewLine(Sizes sizes, IntPtr memData, ulong memSize, ulong baseDisplayAddr) | |
| { | |
| ImGui.Text("Preview as:"); | |
| ImGui.SameLine(); | |
| ImGui.SetNextItemWidth(128); | |
| if (ImGui.BeginCombo("##combo_type", DataTypeGetDesc(_previewDataType), ImGuiComboFlags.HeightLargest)) | |
| { | |
| foreach (ImGuiDataType dataType in Enum.GetValues(typeof(ImGuiDataType))) | |
| { | |
| if (ImGui.Selectable(DataTypeGetDesc(dataType), _previewDataType == dataType)) | |
| _previewDataType = dataType; | |
| } | |
| ImGui.EndCombo(); | |
| } | |
| ImGui.SameLine(); | |
| //ImGui.SetNextItemWidth(sizes.GlyphWidth * 6.0f + ImGui.GetStyle().FramePadding.X * 2.0f + ImGui.GetStyle().ItemInnerSpacing.X); | |
| ImGui.SetNextItemWidth(128); | |
| ImGui.Combo("##combo_endianness", ref _previewEndianness, "LE\0BE\0\0"); | |
| if (_dataPreviewAddr != ulong.MaxValue) | |
| { | |
| ValueUnion val = new ValueUnion(); | |
| byte[] buf = new byte[8]; | |
| ulong size = Math.Min((ulong)buf.Length, memSize - _dataPreviewAddr); | |
| for (ulong i = 0; i < size; i++) | |
| { | |
| buf[i] = ReadFn != null ? ReadFn(memData, _dataPreviewAddr + i, UserData) : Marshal.ReadByte(memData + (int)(_dataPreviewAddr + i)); | |
| val.data[i] = buf[i]; | |
| } | |
| switch(_previewDataType) | |
| { | |
| case ImGuiDataType.S8: | |
| ImGui.Text("Dec: " + val.i8); | |
| break; | |
| case ImGuiDataType.U8: | |
| ImGui.Text("Dec: " + val.u8); | |
| break; | |
| case ImGuiDataType.S16: | |
| ImGui.Text("Dec: " + val.i16); | |
| break; | |
| case ImGuiDataType.U16: | |
| ImGui.Text("Dec: " + val.u16); | |
| break; | |
| case ImGuiDataType.S32: | |
| ImGui.Text("Dec: " + val.i32); | |
| break; | |
| case ImGuiDataType.U32: | |
| ImGui.Text("Dec: " + val.u32); | |
| break; | |
| case ImGuiDataType.S64: | |
| ImGui.Text("Dec: " + val.i64); | |
| break; | |
| case ImGuiDataType.U64: | |
| ImGui.Text("Dec: " + val.u64); | |
| break; | |
| case ImGuiDataType.Float: | |
| ImGui.Text("Dec: " + val.flt); | |
| break; | |
| case ImGuiDataType.Double: | |
| ImGui.Text("Dec: " + val.dbl); | |
| break; | |
| case ImGuiDataType.Bool: | |
| ImGui.Text("Dec: " + val.boolean); | |
| break; | |
| default: | |
| ImGui.Text("Dec: " + BitConverter.ToInt32(buf, 0)); | |
| break; | |
| } | |
| //ImGui.Text("Dec: " + BitConverter.ToInt32(buf, 0)); | |
| ImGui.Text("Hex: " + BitConverter.ToString(buf).Replace("-", "")); | |
| ImGui.Text("Bin: " + Convert.ToString(BitConverter.ToInt32(buf, 0), 2).PadLeft(32, '0')); | |
| } | |
| } | |
| private string DataTypeGetDesc(ImGuiDataType dataType) | |
| { | |
| switch (dataType) | |
| { | |
| case ImGuiDataType.S8: return "Int8"; | |
| case ImGuiDataType.U8: return "Uint8"; | |
| case ImGuiDataType.S16: return "Int16"; | |
| case ImGuiDataType.U16: return "Uint16"; | |
| case ImGuiDataType.S32: return "Int32"; | |
| case ImGuiDataType.U32: return "Uint32"; | |
| case ImGuiDataType.S64: return "Int64"; | |
| case ImGuiDataType.U64: return "Uint64"; | |
| case ImGuiDataType.Float: return "Float"; | |
| case ImGuiDataType.Double: return "Double"; | |
| case ImGuiDataType.Bool: return "Bool"; | |
| default: return "Unknown"; | |
| } | |
| } | |
| private struct Sizes | |
| { | |
| public int AddrDigitsCount; | |
| public float LineHeight; | |
| public float GlyphWidth; | |
| public float HexCellWidth; | |
| public float SpacingBetweenMidCols; | |
| public float PosHexStart; | |
| public float PosHexEnd; | |
| public float PosAsciiStart; | |
| public float PosAsciiEnd; | |
| public float WindowWidth; | |
| } | |
| [StructLayout(LayoutKind.Explicit, Size = 8)] | |
| private unsafe struct ValueUnion | |
| { | |
| [FieldOffset(0)] | |
| public bool boolean; | |
| [FieldOffset(0)] | |
| public byte u8; | |
| [FieldOffset(0)] | |
| public sbyte i8; | |
| [FieldOffset(0)] | |
| public ushort u16; | |
| [FieldOffset(0)] | |
| public short i16; | |
| [FieldOffset(0)] | |
| public uint u32; | |
| [FieldOffset(0)] | |
| public int i32; | |
| [FieldOffset(0)] | |
| public ulong u64; | |
| [FieldOffset(0)] | |
| public long i64; | |
| [FieldOffset(0)] | |
| public float flt; | |
| [FieldOffset(0)] | |
| public double dbl; | |
| [FieldOffset(0)] | |
| public fixed byte data[8]; | |
| } | |
| } | |
| public unsafe sealed class ImGuiListClipperEx | |
| { | |
| private ImGuiListClipper *clipper = null; | |
| public int DisplayStart | |
| { | |
| get | |
| { | |
| if(clipper == null) | |
| return 0; | |
| return clipper->DisplayStart; | |
| } | |
| } | |
| public int DisplayEnd | |
| { | |
| get | |
| { | |
| if(clipper == null) | |
| return 0; | |
| return clipper->DisplayEnd; | |
| } | |
| } | |
| public ImGuiListClipperEx() | |
| { | |
| clipper = ImGuiNative.ImGuiListClipper_ImGuiListClipper(); | |
| } | |
| public void Begin(int totalCount, float lineHeight) | |
| { | |
| if(clipper != null) | |
| ImGuiNative.ImGuiListClipper_Begin(clipper, totalCount, lineHeight); | |
| } | |
| public bool Step() | |
| { | |
| if(clipper != null) | |
| return ImGuiNative.ImGuiListClipper_Step(clipper) > 0; | |
| return false; | |
| } | |
| public void Dispose() | |
| { | |
| if(clipper != null) | |
| ImGuiNative.ImGuiListClipper_destroy(clipper); | |
| clipper = null; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment