using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Drawing; using System.Linq; using System.Windows.Forms; using ReClassNET.Extensions; using ReClassNET.Memory; using ReClassNET.Nodes; using ReClassNET.Util; namespace ReClassNET.UI { public partial class MemoryViewControl : ScrollableCustomControl { /// /// Contains informations about a selected node. /// public class SelectedNodeInfo { /// /// The selected node. /// public BaseNode Node { get; } /// /// The memory this node uses. /// public MemoryBuffer Memory { get; } /// /// The address of the node in the remote process. /// public IntPtr Address { get; } public int Level { get; } public SelectedNodeInfo(BaseNode node, MemoryBuffer memory, IntPtr address, int level) { Contract.Requires(node != null); Contract.Requires(memory != null); Node = node; Memory = memory; Address = address; Level = level; } } private ClassNode classNode; private readonly List hotSpots = new List(); private readonly List selectedNodes = new List(); private HotSpot selectionCaret; private HotSpot selectionAnchor; private readonly FontEx font; [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public ClassNode ClassNode { get => classNode; set { editBox.Visible = false; ClearSelection(); OnSelectionChanged(); classNode = value; VerticalScroll.Value = VerticalScroll.Minimum; if (classNode != null && Memory?.Process != null) { classNode.UpdateAddress(Memory.Process); } Invalidate(); } } [Browsable(false)] [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public MemoryBuffer Memory { get; set; } public ContextMenuStrip NodeContextMenuStrip { get; set; } public event EventHandler SelectionChanged; public event NodeClickEventHandler ChangeClassTypeClick; public event NodeClickEventHandler ChangeWrappedTypeClick; public event NodeClickEventHandler ChangeEnumTypeClick; private readonly MemoryPreviewPopUp memoryPreviewPopUp; public MemoryViewControl() { InitializeComponent(); if (Program.DesignMode) { return; } font = Program.MonoSpaceFont; editBox.Font = font; memoryPreviewPopUp = new MemoryPreviewPopUp(font); } protected override void OnLoad(EventArgs e) { base.OnLoad(e); VerticalScroll.Enabled = true; VerticalScroll.Visible = true; VerticalScroll.SmallChange = 10; HorizontalScroll.Enabled = true; HorizontalScroll.Visible = true; HorizontalScroll.SmallChange = 100; } protected override void OnPaint(PaintEventArgs e) { base.OnPaint(e); if (DesignMode) { e.Graphics.FillRectangle(Brushes.White, ClientRectangle); return; } hotSpots.Clear(); using (var brush = new SolidBrush(Program.Settings.BackgroundColor)) { e.Graphics.FillRectangle(brush, ClientRectangle); } if (ClassNode == null) { return; } if (Memory.Process != null) { ClassNode.UpdateAddress(Memory.Process); } if (memoryPreviewPopUp.Visible) { memoryPreviewPopUp.UpdateMemory(); } Memory.Size = ClassNode.MemorySize; Memory.Update(ClassNode.Address); var view = new ViewInfo { Settings = Program.Settings, Context = e.Graphics, Font = font, Memory = Memory, CurrentTime = DateTime.UtcNow, ClientArea = ClientRectangle, HotSpots = hotSpots, Address = classNode.Address, Level = 0, MultipleNodesSelected = selectedNodes.Count > 1 }; try { var drawnSize = ClassNode.Draw( view, -HorizontalScroll.Value, -VerticalScroll.Value * font.Height ); drawnSize.Width += 50; /*foreach (var spot in hotSpots.Where(h => h.Type == HotSpotType.Select)) { e.Graphics.DrawRectangle(new Pen(new SolidBrush(Color.FromArgb(150, 255, 0, 0)), 1), spot.Rect); }*/ if (drawnSize.Height > ClientSize.Height) { VerticalScroll.Enabled = true; VerticalScroll.LargeChange = ClientSize.Height / font.Height; VerticalScroll.Maximum = (drawnSize.Height - ClientSize.Height) / font.Height + VerticalScroll.LargeChange; } else { VerticalScroll.Enabled = false; VerticalScroll.Value = VerticalScroll.Minimum; } if (drawnSize.Width > ClientSize.Width) { HorizontalScroll.Enabled = true; HorizontalScroll.LargeChange = ClientSize.Width; HorizontalScroll.Maximum = drawnSize.Width - ClientSize.Width + HorizontalScroll.LargeChange; } else { HorizontalScroll.Enabled = false; HorizontalScroll.Value = HorizontalScroll.Minimum; } } catch (Exception) { Debug.Assert(false); throw; } } private void OnSelectionChanged() { SelectionChanged?.Invoke(this, EventArgs.Empty); } #region Process Input protected override void OnMouseClick(MouseEventArgs e) { Contract.Requires(e != null); var invalidate = false; foreach (var hotSpot in hotSpots) { if (hotSpot.Rect.Contains(e.Location)) { var hitObject = hotSpot.Node; if (hotSpot.Type == HotSpotType.OpenClose) { hitObject.ToggleLevelOpen(hotSpot.Level); invalidate = true; break; } if (hotSpot.Type == HotSpotType.Click) { hitObject.Update(hotSpot); invalidate = true; break; } if (hotSpot.Type == HotSpotType.Select) { if (e.Button == MouseButtons.Left) { if (ModifierKeys == Keys.None) { ClearSelection(); hitObject.IsSelected = true; selectedNodes.Add(hotSpot); OnSelectionChanged(); selectionAnchor = selectionCaret = hotSpot; } else if (ModifierKeys == Keys.Control) { hitObject.IsSelected = !hitObject.IsSelected; if (hitObject.IsSelected) { selectedNodes.Add(hotSpot); } else { selectedNodes.Remove(selectedNodes.FirstOrDefault(c => c.Node == hitObject)); } OnSelectionChanged(); } else if (ModifierKeys == Keys.Shift) { if (selectedNodes.Count > 0) { var selectedNode = selectedNodes[0].Node; if (hitObject.GetParentContainer() != null && selectedNode.GetParentContainer() != hitObject.GetParentContainer()) { continue; } if (hotSpot.Node is BaseContainerNode) { continue; } var first = Utils.Min(selectedNodes[0], hotSpot, h => h.Node.Offset); var last = first == hotSpot ? selectedNodes[0] : hotSpot; ClearSelection(); var containerNode = selectedNode.GetParentContainer(); foreach (var spot in containerNode.Nodes .SkipWhile(n => n != first.Node) .TakeUntil(n => n == last.Node) .Select(n => new HotSpot { Address = (IntPtr)(containerNode.Offset + n.Offset), Node = n, Memory = first.Memory, Level = first.Level })) { spot.Node.IsSelected = true; selectedNodes.Add(spot); } OnSelectionChanged(); selectionAnchor = first; selectionCaret = last; } } } else if (e.Button == MouseButtons.Right) { // If there is only one selected node, select the node the user clicked at. if (selectedNodes.Count <= 1) { ClearSelection(); hitObject.IsSelected = true; selectedNodes.Add(hotSpot); OnSelectionChanged(); selectionAnchor = selectionCaret = hotSpot; } ShowNodeContextMenu(e.Location); } invalidate = true; } else if (hotSpot.Type == HotSpotType.Context) { ShowNodeContextMenu(e.Location); break; } else if (hotSpot.Type == HotSpotType.Delete) { hotSpot.Node.GetParentContainer().RemoveNode(hotSpot.Node); invalidate = true; break; } else if (hotSpot.Type == HotSpotType.ChangeClassType || hotSpot.Type == HotSpotType.ChangeWrappedType || hotSpot.Type == HotSpotType.ChangeEnumType) { var handler = hotSpot.Type == HotSpotType.ChangeClassType ? ChangeClassTypeClick : hotSpot.Type == HotSpotType.ChangeWrappedType ? ChangeWrappedTypeClick : ChangeEnumTypeClick; handler?.Invoke(this, new NodeClickEventArgs(hitObject, hotSpot.Address, hotSpot.Memory, e.Button, e.Location)); break; } } } editBox.Visible = false; if (invalidate) { Invalidate(); } base.OnMouseClick(e); } protected override void OnMouseDoubleClick(MouseEventArgs e) { Contract.Requires(e != null); editBox.Visible = false; var invalidate = false; // Order the hotspots: 1. DoubleClick 2. Click 3. Edit 4. Select var spots = hotSpots.Where(h => h.Type == HotSpotType.DoubleClick) .Concat(hotSpots.Where(h => h.Type == HotSpotType.Click)) .Concat(hotSpots.Where(h => h.Type == HotSpotType.Edit)) .Concat(hotSpots.Where(h => h.Type == HotSpotType.Select)); foreach (var hotSpot in spots) { if (hotSpot.Rect.Contains(e.Location)) { if (hotSpot.Type == HotSpotType.DoubleClick || hotSpot.Type == HotSpotType.Click) { hotSpot.Node.Update(hotSpot); invalidate = true; break; } if (hotSpot.Type == HotSpotType.Edit) { editBox.BackColor = Program.Settings.SelectedColor; editBox.HotSpot = hotSpot; editBox.Visible = true; editBox.ReadOnly = hotSpot.Id == HotSpot.ReadOnlyId; break; } if (hotSpot.Type == HotSpotType.Select) { hotSpot.Node.ToggleLevelOpen(hotSpot.Level); invalidate = true; break; } } } if (invalidate) { Invalidate(); } base.OnMouseDoubleClick(e); } private Point toolTipPosition; protected override void OnMouseHover(EventArgs e) { Contract.Requires(e != null); base.OnMouseHover(e); if (selectedNodes.Count > 1) { var memorySize = selectedNodes.Sum(h => h.Node.MemorySize); nodeInfoToolTip.Show($"{selectedNodes.Count} Nodes selected, {memorySize} bytes", this, toolTipPosition.OffsetEx(16, 16)); } else { foreach (var spot in hotSpots.Where(h => h.Type == HotSpotType.Select)) { if (spot.Rect.Contains(toolTipPosition)) { if (spot.Node.UseMemoryPreviewToolTip(spot, spot.Memory, out var previewAddress)) { memoryPreviewPopUp.InitializeMemory(spot.Memory.Process, previewAddress); memoryPreviewPopUp.Show(this, toolTipPosition.OffsetEx(16, 16)); } else { var text = spot.Node.GetToolTipText(spot, spot.Memory); if (!string.IsNullOrEmpty(text)) { nodeInfoToolTip.Show(text, this, toolTipPosition.OffsetEx(16, 16)); } } return; } } } } protected override void OnMouseMove(MouseEventArgs e) { Contract.Requires(e != null); base.OnMouseMove(e); if (e.Location != toolTipPosition) { toolTipPosition = e.Location; nodeInfoToolTip.Hide(this); if (memoryPreviewPopUp.Visible) { memoryPreviewPopUp.Close(); Invalidate(); } ResetMouseEventArgs(); } } protected override void OnMouseWheel(MouseEventArgs e) { if (memoryPreviewPopUp.Visible) { memoryPreviewPopUp.HandleMouseWheelEvent(e); } else { base.OnMouseWheel(e); } } protected override void OnScroll(ScrollEventArgs e) { Contract.Requires(e != null); base.OnScroll(e); editBox.Visible = false; } protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if (editBox.Visible == false) // Only process keys if the edit field is not visible. { var key = keyData & Keys.KeyCode; var modifier = keyData & Keys.Modifiers; if (selectedNodes.Count > 0) { if (key == Keys.Menu) { ShowNodeContextMenu(new Point(10, 10)); return true; } if ((key == Keys.Down || key == Keys.Up) && selectionCaret != null && selectionAnchor != null) { HotSpot toSelect; bool isAtEnd; var query = hotSpots .Where(h => h.Type == HotSpotType.Select) .Where(h => h.Node.GetParentContainer() == selectionCaret.Node.GetParentContainer()); if (key == Keys.Down) { var temp = query .SkipUntil(h => h.Node == selectionCaret.Node) .ToList(); toSelect = temp.FirstOrDefault(); isAtEnd = toSelect != null && toSelect == temp.LastOrDefault(); } else { var temp = query .TakeWhile(h => h.Node != selectionCaret.Node) .ToList(); toSelect = temp.LastOrDefault(); isAtEnd = toSelect != null && toSelect == temp.FirstOrDefault(); } if (toSelect != null && !(toSelect.Node is ClassNode)) { if (modifier != Keys.Shift) { selectionAnchor = selectionCaret = toSelect; } else { selectionCaret = toSelect; } var first = Utils.Min(selectionAnchor, selectionCaret, h => h.Node.Offset); var last = first == selectionAnchor ? selectionCaret : selectionAnchor; selectedNodes.ForEach(h => h.Node.ClearSelection()); selectedNodes.Clear(); var containerNode = toSelect.Node.GetParentContainer(); foreach (var spot in containerNode.Nodes .SkipWhile(n => n != first.Node) .TakeUntil(n => n == last.Node) .Select(n => new HotSpot { Address = (IntPtr)(containerNode.Offset + n.Offset), Node = n, Memory = toSelect.Memory, Level = toSelect.Level })) { spot.Node.IsSelected = true; selectedNodes.Add(spot); } OnSelectionChanged(); if (isAtEnd) { DoScroll(ScrollOrientation.VerticalScroll, key == Keys.Down ? 1 : - 1); } Invalidate(); return true; } } else if (key == Keys.Left || key == Keys.Right) { if (selectedNodes.Count == 1) { var selected = selectedNodes[0]; selected.Node.SetLevelOpen(selected.Level, key == Keys.Right); } } } else if (key == Keys.Down || key == Keys.Up) { // If no node is selected, try to select the first one. var selection = hotSpots .Where(h => h.Type == HotSpotType.Select) .WhereNot(h => h.Node is ClassNode) .FirstOrDefault(); if (selection != null) { selectionAnchor = selectionCaret = selection; selection.Node.IsSelected = true; selectedNodes.Add(selection); OnSelectionChanged(); return true; } } } return base.ProcessCmdKey(ref msg, keyData); } #endregion #region Event Handler protected override void OnSizeChanged(EventArgs e) { base.OnSizeChanged(e); Invalidate(); } private void repaintTimer_Tick(object sender, EventArgs e) { if (DesignMode) { return; } Invalidate(false); } private void editBox_Committed(object sender, EventArgs e) { var hotspotTextBox = sender as HotSpotTextBox; var hotSpot = hotspotTextBox?.HotSpot; if (hotSpot != null) { try { hotSpot.Node.Update(hotSpot); } catch (Exception ex) { Program.Logger.Log(ex); } Invalidate(); } } #endregion /// /// Gets informations about all selected nodes. /// /// A list with informations about all selected nodes. public IReadOnlyList GetSelectedNodes() { return selectedNodes .Select(h => new SelectedNodeInfo(h.Node, h.Memory, h.Address, h.Level)) .ToList(); } /// /// Selects the given nodes. /// /// public void SetSelectedNodes(IEnumerable nodes) { selectedNodes.ForEach(h => h.Node.ClearSelection()); selectedNodes.Clear(); selectedNodes.AddRange(nodes.Select(i => new HotSpot { Type = HotSpotType.Select, Node = i.Node, Memory = i.Memory, Address = i.Address, Level = i.Level })); selectedNodes.ForEach(h => h.Node.IsSelected = true); OnSelectionChanged(); } /// /// Shows the context menu at the given point. /// /// The location where the context menu should be shown. private void ShowNodeContextMenu(Point location) { NodeContextMenuStrip?.Show(this, location); } /// /// Resets the selection state of all selected nodes. /// public void ClearSelection() { selectionAnchor = selectionCaret = null; selectedNodes.ForEach(h => h.Node.ClearSelection()); selectedNodes.Clear(); OnSelectionChanged(); //Invalidate(); } } }