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