using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Contracts; using System.Drawing; using ReClassNET.Extensions; using ReClassNET.Memory; using ReClassNET.UI; using ReClassNET.Util; namespace ReClassNET.Nodes { public delegate void NodeEventHandler(BaseNode sender); [DebuggerDisplay("{" + nameof(DebuggerDisplay) + ",nq}")] [ContractClass(typeof(BaseNodeContract))] public abstract class BaseNode { private string DebuggerDisplay => $"Type: {GetType().Name} Name: {Name} Offset: 0x{Offset.ToString("X")}"; internal static readonly List NodeInfoReader = new List(); protected static readonly int TextPadding = Icons.Dimensions; protected static readonly int HiddenHeight = 0; private static int nodeIndex = 0; private string name = string.Empty; private string comment = string.Empty; /// Gets or sets the offset of the node. public int Offset { get; set; } /// Gets or sets the name of the node. If a new name was set the property changed event gets fired. public virtual string Name { get => name; set { if (value != null && name != value) { name = value; NameChanged?.Invoke(this); } } } /// Gets or sets the comment of the node. public string Comment { get => comment; set { if (value != null && comment != value) { comment = value; CommentChanged?.Invoke(this); } } } /// Gets or sets the parent node. public BaseNode ParentNode { get; internal set; } /// Gets a value indicating whether this node is wrapped into an other node. public bool IsWrapped => ParentNode is BaseWrapperNode; /// Gets or sets a value indicating whether this node is hidden. public bool IsHidden { get; set; } /// Gets or sets a value indicating whether this node is selected. public bool IsSelected { get; set; } /// Size of the node in bytes. public abstract int MemorySize { get; } public event NodeEventHandler NameChanged; public event NodeEventHandler CommentChanged; protected GrowingList LevelsOpen { get; } = new GrowingList(false); [ContractInvariantMethod] private void ObjectInvariants() { Contract.Invariant(name != null); Contract.Invariant(comment != null); Contract.Invariant(Offset >= 0); Contract.Invariant(LevelsOpen != null); } /// /// Creates an instance of the specific node type. /// /// The of the node. /// An instance of the node type or null if the type is not a valid node type. public static BaseNode CreateInstanceFromType(Type nodeType) { return CreateInstanceFromType(nodeType, true); } /// /// Creates an instance of the specific node type. /// /// The of the node. /// If true gets called for the new node. /// An instance of the node type or null if the type is not a valid node type. public static BaseNode CreateInstanceFromType(Type nodeType, bool callInitialize) { var node = Activator.CreateInstance(nodeType) as BaseNode; if (callInitialize) { node?.Initialize(); } return node; } /// Constructor which sets a unique . protected BaseNode() { Contract.Ensures(name != null); Contract.Ensures(comment != null); Name = $"N{nodeIndex++:X08}"; Comment = string.Empty; LevelsOpen[0] = true; } public abstract void GetUserInterfaceInfo(out string name, out Image icon); public virtual bool UseMemoryPreviewToolTip(HotSpot spot, out IntPtr address) { Contract.Requires(spot != null); address = IntPtr.Zero; return false; } /// Gets informations about this node to show in a tool tip. /// The spot. /// The information to show in a tool tip or null if no information should be shown. public virtual string GetToolTipText(HotSpot spot) { Contract.Requires(spot != null); return null; } /// Called when the node was created. Does not get called after loading a project. public virtual void Initialize() { } /// Initializes this object from the given node. It copies the name and the comment. /// The node to copy from. public virtual void CopyFromNode(BaseNode node) { Contract.Requires(node != null); Name = node.Name; Comment = node.Comment; Offset = node.Offset; } /// /// Gets the parent container of the node. /// /// public BaseContainerNode GetParentContainer() { var parentNode = ParentNode; while (parentNode != null) { if (parentNode is BaseContainerNode containerNode) { return containerNode; } parentNode = parentNode.ParentNode; } if (this is BaseContainerNode containerNode2) { return containerNode2; } return null; } /// /// Gets the parent class of the node. /// /// public ClassNode GetParentClass() { var parentNode = ParentNode; while (parentNode != null) { if (parentNode is ClassNode classNode) { return classNode; } parentNode = parentNode.ParentNode; } return null; } /// /// Gets the root wrapper node if this node is the inner node of a wrapper chain. /// /// The root or null if this node is not wrapped or isn't itself a wrapper node. public BaseWrapperNode GetRootWrapperNode() { BaseWrapperNode rootWrapperNode = null; var parentNode = ParentNode; while (parentNode is BaseWrapperNode wrapperNode) { rootWrapperNode = wrapperNode; parentNode = parentNode.ParentNode; } // Test if this node is the root wrapper node. if (rootWrapperNode == null) { if (this is BaseWrapperNode wrapperNode) { return wrapperNode; } } return rootWrapperNode; } /// Clears the selection of the node. public virtual void ClearSelection() { IsSelected = false; } /// Draws the node. /// The view information. /// The x coordinate. /// The y coordinate. /// The pixel size the node occupies. public abstract Size Draw(ViewInfo view, int x, int y); /// /// Calculates the height of the node if drawn. /// This method is used to determine if a node outside the visible area should be drawn. /// The returned height must be equal to the height which is returned by the method. /// /// The view information. /// The calculated height. public abstract int CalculateDrawnHeight(ViewInfo view); /// Updates the node from the given . Sets the and of the node. /// The spot. public virtual void Update(HotSpot spot) { Contract.Requires(spot != null); if (spot.Id == HotSpot.NameId) { Name = spot.Text; } else if (spot.Id == HotSpot.CommentId) { Comment = spot.Text; } } /// Toggles the specified level. /// The level to toggle. internal void ToggleLevelOpen(int level) { LevelsOpen[level] = !LevelsOpen[level]; } /// Sets the specific level. /// The level to set. /// True to open. internal void SetLevelOpen(int level, bool open) { LevelsOpen[level] = open; } /// Adds a the user can interact with. /// The view information. /// The spot. /// The text to edit. /// The id of the spot. /// The type of the spot. protected void AddHotSpot(ViewInfo view, Rectangle spot, string text, int id, HotSpotType type) { Contract.Requires(view != null); Contract.Requires(view.Memory != null); Contract.Requires(text != null); if (spot.Top > view.ClientArea.Bottom || spot.Bottom < 0) { return; } view.HotSpots.Add(new HotSpot { Rect = spot, Text = text, Address = view.Address + Offset, Id = id, Type = type, Node = this, Level = view.Level, Process = view.Process, Memory = view.Memory }); } /// Draws the specific text and adds a if is not . /// The view information. /// The x coordinate. /// The y coordinate. /// The color of the text. /// Id for the clickable area. /// The text to draw. /// The new x coordinate after drawing the text. protected int AddText(ViewInfo view, int x, int y, Color color, int hitId, string text) { Contract.Requires(view != null); Contract.Requires(view.Context != null); Contract.Requires(view.Font != null); Contract.Requires(text != null); var width = Math.Max(text.Length, hitId != HotSpot.NoneId ? 1 : 0) * view.Font.Width; if (y >= -view.Font.Height && y + view.Font.Height <= view.ClientArea.Bottom + view.Font.Height) { if (hitId != HotSpot.NoneId) { var rect = new Rectangle(x, y, width, view.Font.Height); AddHotSpot(view, rect, text, hitId, HotSpotType.Edit); } view.Context.DrawStringEx(text, view.Font.Font, color, x, y); /*using (var brush = new SolidBrush(color)) { view.Context.DrawString(text, view.Font.Font, brush, x, y); }*/ } return x + width; } /// Draws the address and of the node. /// The view information. /// The x coordinate. /// The y coordinate. /// The new x coordinate after drawing the text. protected int AddAddressOffset(ViewInfo view, int x, int y) { Contract.Requires(view != null); Contract.Requires(view.Context != null); Contract.Requires(view.Font != null); if (view.Settings.ShowNodeOffset) { x = AddText(view, x, y, view.Settings.OffsetColor, HotSpot.NoneId, $"{Offset:X04}") + view.Font.Width; } if (view.Settings.ShowNodeAddress) { x = AddText(view, x, y, view.Settings.AddressColor, HotSpot.AddressId, (view.Address + Offset).ToString(Constants.AddressHexFormat)) + view.Font.Width; } return x; } /// Draws a bar which indicates the selection status of the node. A for this area gets added too. /// The view information. /// The x coordinate. /// The y coordinate. /// The height of the bar. protected void AddSelection(ViewInfo view, int x, int y, int height) { Contract.Requires(view != null); Contract.Requires(view.Context != null); if (y > view.ClientArea.Bottom || y + height < 0 || IsWrapped) { return; } if (IsSelected) { using (var brush = new SolidBrush(view.Settings.SelectedColor)) { view.Context.FillRectangle(brush, 0, y, view.ClientArea.Right, height); } } AddHotSpot(view, new Rectangle(0, y, view.ClientArea.Right - (IsSelected ? 16 : 0), height), string.Empty, HotSpot.NoneId, HotSpotType.Select); } /// Draws an icon and adds a if is not . /// The view information. /// The x coordinate. /// The y coordinate. /// The icon. /// The id of the spot. /// The type of the spot. /// The new x coordinate after drawing the icon. protected int AddIcon(ViewInfo view, int x, int y, Image icon, int id, HotSpotType type) { Contract.Requires(view != null); Contract.Requires(view.Context != null); Contract.Requires(icon != null); if (y > view.ClientArea.Bottom || y + Icons.Dimensions < 0) { return x + Icons.Dimensions; } view.Context.DrawImage(icon, x + 2, y, Icons.Dimensions, Icons.Dimensions); if (id != HotSpot.NoneId) { AddHotSpot(view, new Rectangle(x, y, Icons.Dimensions, Icons.Dimensions), string.Empty, id, type); } return x + Icons.Dimensions; } /// Adds a togglable Open/Close icon. /// The view information. /// The x coordinate. /// The y coordinate. /// The new x coordinate after drawing the icon. protected int AddOpenCloseIcon(ViewInfo view, int x, int y) { Contract.Requires(view != null); Contract.Requires(view.Context != null); if (y > view.ClientArea.Bottom || y + Icons.Dimensions < 0) { return x + Icons.Dimensions; } return AddIcon(view, x, y, LevelsOpen[view.Level] ? Icons.OpenCloseOpen : Icons.OpenCloseClosed, 0, HotSpotType.OpenClose); } /// Draws a context drop icon if the node is selected. /// The view information. /// The y coordinate. protected void AddContextDropDownIcon(ViewInfo view, int y) { Contract.Requires(view != null); Contract.Requires(view.Context != null); if (view.MultipleNodesSelected || y > view.ClientArea.Bottom || y + Icons.Dimensions < 0 || IsWrapped) { return; } if (IsSelected) { AddIcon(view, 0, y, Icons.DropArrow, 0, HotSpotType.Context); } } /// Draws a delete icon if the node is selected. /// The view information. /// The y coordinate. protected void AddDeleteIcon(ViewInfo view, int y) { Contract.Requires(view != null); Contract.Requires(view.Context != null); if (y > view.ClientArea.Bottom || y + Icons.Dimensions < 0 || IsWrapped) { return; } if (IsSelected) { AddIcon(view, view.ClientArea.Right - Icons.Dimensions, y, Icons.Delete, 0, HotSpotType.Delete); } } /// Draws the . /// The view information. /// The x coordinate. /// The y coordinate. /// The new x coordinate after drawing the comment. protected virtual int AddComment(ViewInfo view, int x, int y) { Contract.Requires(view != null); Contract.Requires(view.Context != null); Contract.Requires(view.Font != null); x = AddText(view, x, y, view.Settings.CommentColor, HotSpot.NoneId, "//"); x = AddText(view, x, y, view.Settings.CommentColor, HotSpot.CommentId, Comment) + view.Font.Width; return x; } /// Draws a vertical line to show the hidden state. /// The view information. /// The x coordinate. /// The y coordinate. /// The size of the drawing. protected Size DrawHidden(ViewInfo view, int x, int y) { Contract.Requires(view != null); Contract.Requires(view.Context != null); using (var brush = new SolidBrush(IsSelected ? view.Settings.SelectedColor : view.Settings.HiddenColor)) { view.Context.FillRectangle(brush, 0, y, view.ClientArea.Right, 1); } return new Size(0, HiddenHeight); } /// Draws an error indicator if the used memory buffer is not valid. /// The view information. /// The y coordinate. protected void DrawInvalidMemoryIndicatorIcon(ViewInfo view, int y) { if (!view.Memory.ContainsValidData) { AddIcon(view, 0, y, Properties.Resources.B16x16_Error, HotSpot.NoneId, HotSpotType.None); } } } [ContractClassFor(typeof(BaseNode))] internal abstract class BaseNodeContract : BaseNode { public override int MemorySize { get { Contract.Ensures(Contract.Result() >= 0); throw new NotImplementedException(); } } public override Size Draw(ViewInfo view, int x, int y) { Contract.Requires(view != null); throw new NotImplementedException(); } public override int CalculateDrawnHeight(ViewInfo view) { Contract.Requires(view != null); throw new NotImplementedException(); } } }