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; } }