diff --git a/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs b/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs index 87a5fa057e9..bf39374b715 100644 --- a/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs +++ b/src/BizHawk.Client.EmuHawk/tools/HexEditor/HexEditor.cs @@ -1,3 +1,4 @@ +using System.Buffers; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; @@ -87,6 +88,11 @@ private static FilesystemFilterSet CreateBinaryDumpFSFilterSet(string ext) [RequiredService] private IEmulator Emulator { get; set; } + [OptionalService] + private IDebuggable _maybeDebuggableCore { get; set; } + + private IMemoryCallbackSystem/*?*/ MemoryCallbacks = null; + private readonly int _fontWidth; private readonly int _fontHeight; @@ -154,6 +160,7 @@ internal class ColorConfig private SolidBrush _freezeHighlightBrush; private SolidBrush _highlightBrush; private SolidBrush _secondaryHighlightBrush; + private SolidBrush _heatmapHighlightBrush; private string _windowTitle = "Hex Editor"; @@ -270,12 +277,50 @@ public override void Restart() ResetScrollBar(); } + MemoryCallbacks?.Remove(MemReadCallback); + RerentHeatmap(_domain.Size); + MemoryCallbacks = _maybeDebuggableCore?.MemoryCallbacksAvailable() is true + ? _maybeDebuggableCore.MemoryCallbacks + : null; + MemoryCallbacks?.Add(new MemoryCallback( + scope: MemoryDomains.HasSystemBus + ? MemoryDomains.SystemBus.Name + : MemoryCallbacks.AvailableScopes[0], + MemoryCallbackType.Read, + name: "Hex Editor heatmap hook", + MemReadCallback, + address: null, + mask: null)); + SetDataSize(DataSize); SetHeader(); GeneralUpdate(); } + private ushort[]/*?*/ _heatmap = null; + + private ushort _heatmapTimestamp = 0; + + /// + private const uint MAX_ARRAY_INDEX = int.MaxValue - 1; + + private void RerentHeatmap(long size) + { + if (_heatmap is not null) ArrayPool.Shared.Return(_heatmap, clearArray: false); + _heatmap = size - 1L <= MAX_ARRAY_INDEX + ? ArrayPool.Shared.Rent((int) size) + : null; + _heatmapTimestamp = 0; + } + + private uint? MemReadCallback(uint address, uint value, uint flags) + { + if (_heatmap is null || _heatmap.Length <= address) return null; + _heatmap[address] = _heatmapTimestamp++; + return null; + } + public void SetToAddresses(IEnumerable addresses, MemoryDomain domain, WatchSize size) { DataSize = (int)size; @@ -528,6 +573,7 @@ private void LoadConfigSettings() _freezeHighlightBrush = new SolidBrush(Colors.HighlightFreeze); _highlightBrush = new SolidBrush(Colors.Highlight); _secondaryHighlightBrush = new SolidBrush(Color.FromArgb(0x44, Colors.Highlight)); + _heatmapHighlightBrush = _secondaryHighlightBrush.GetMutableCopy(); } private void CloseHexFind() @@ -1992,6 +2038,42 @@ private void MemoryViewerBox_Paint(object sender, PaintEventArgs e) } } + for (var i = 0; i < _rowsVisible; i++) + { + var row = HexScrollBar.Value + (long) i; + var baseAddr = row << 4; + if (_heatmap.Length < baseAddr + 16) break; //TODO off-by-one here? not sure if `<` or `<=` + for (var j = 0; j < 16; j += DataSize) + { + var address = unchecked((int) (baseAddr + j)); + var heatRaw = DataSize switch + { + 4 => Math.Max( + Math.Max(_heatmap[address], _heatmap[address + 1]), + Math.Max(_heatmap[address + 2], _heatmap[address + 3])), + 2 => Math.Max(_heatmap[address], _heatmap[address + 1]), + _ => unchecked((int) _heatmap[address]), + }; + // questionable math alert + const int TIMESTAMP_WRAP_COUNT = ushort.MaxValue + 1; + var heat = (_heatmapTimestamp - heatRaw) % TIMESTAMP_WRAP_COUNT; + var heatScaled = unchecked((byte) (heat * 255.0f / TIMESTAMP_WRAP_COUNT)); + + var point = GetAddressCoordinates(address); + var textX = (int)GetTextX(address); + var textPoint = new Point(textX, point.Y); + + var rect = new Rectangle(point, new Size(_fontWidth * 2 * DataSize + 2, _fontHeight)); + var textRect = new Rectangle(textPoint, new Size(_fontWidth * DataSize, _fontHeight)); + + _heatmapHighlightBrush.Color = Color.FromArgb(0xFF - heatScaled, Colors.Highlight); + e.Graphics.FillRectangle(_secondaryHighlightBrush, rect); + e.Graphics.FillRectangle(_secondaryHighlightBrush, textRect); + + e.Graphics.DrawRectangle(_blackPen, rect); + } + } + if (_highlightedAddress.HasValue && IsVisible(_highlightedAddress.Value)) { long addressHighlighted = _highlightedAddress ?? 0;