From c25d354194102c04bbc0948b00863339d8fc3468 Mon Sep 17 00:00:00 2001 From: adelikat Date: Tue, 3 Mar 2020 10:58:48 -0600 Subject: [PATCH] break out some classes out of RamSearchEngine into their own files --- .../tools/RamSearchEngine/IMiniWatch.cs | 72 + .../RamSearchEngine/IMiniWatchDetails.cs | 177 ++ .../{ => RamSearchEngine}/RamSearchEngine.cs | 1873 +++++++---------- 3 files changed, 1063 insertions(+), 1059 deletions(-) create mode 100644 BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs create mode 100644 BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs rename BizHawk.Client.Common/tools/{ => RamSearchEngine}/RamSearchEngine.cs (79%) diff --git a/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs b/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs new file mode 100644 index 0000000000..cb5f59d5e6 --- /dev/null +++ b/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs @@ -0,0 +1,72 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.Common +{ + /// + /// Represents a Ram address for watching in the + /// With the minimal details necessary for searching + /// + internal interface IMiniWatch + { + long Address { get; } + long Previous { get; } // do not store sign extended variables in here. + void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian); + } + + internal sealed class MiniByteWatch : IMiniWatch + { + public long Address { get; } + private byte _previous; + + public MiniByteWatch(MemoryDomain domain, long addr) + { + Address = addr; + _previous = domain.PeekByte(Address % domain.Size); + } + + public long Previous => _previous; + + public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) + { + _previous = domain.PeekByte(Address % domain.Size); + } + } + + internal sealed class MiniWordWatch : IMiniWatch + { + public long Address { get; } + private ushort _previous; + + public MiniWordWatch(MemoryDomain domain, long addr, bool bigEndian) + { + Address = addr; + _previous = domain.PeekUshort(Address % domain.Size, bigEndian); + } + + public long Previous => _previous; + + public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) + { + _previous = domain.PeekUshort(Address, bigEndian); + } + } + + internal sealed class MiniDWordWatch : IMiniWatch + { + public long Address { get; } + private uint _previous; + + public MiniDWordWatch(MemoryDomain domain, long addr, bool bigEndian) + { + Address = addr; + _previous = domain.PeekUint(Address % domain.Size, bigEndian); + } + + public long Previous => _previous; + + public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) + { + _previous = domain.PeekUint(Address, bigEndian); + } + } +} diff --git a/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs b/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs new file mode 100644 index 0000000000..cc0f5a38f3 --- /dev/null +++ b/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs @@ -0,0 +1,177 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.Common +{ + /// + /// Represents a but with added details + /// to do change tracking. These types add more information but at a cost of + /// having to poll the ram address on every update + /// + internal interface IMiniWatchDetails : IMiniWatch + { + int ChangeCount { get; } + + void ClearChangeCount(); + void Update(PreviousType type, MemoryDomain domain, bool bigEndian); + } + + internal sealed class MiniByteWatchDetailed : IMiniWatchDetails + { + public long Address { get; } + + private byte _previous; + private byte _prevFrame; + + public MiniByteWatchDetailed(MemoryDomain domain, long addr) + { + Address = addr; + SetPreviousToCurrent(domain, false); + } + + public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) + { + _previous = _prevFrame = domain.PeekByte(Address % domain.Size); + } + + public long Previous => _previous; + + public int ChangeCount { get; private set; } + + public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) + { + var value = domain.PeekByte(Address % domain.Size); + + if (value != _prevFrame) + { + ChangeCount++; + } + + switch (type) + { + case PreviousType.Original: + case PreviousType.LastSearch: + break; + case PreviousType.LastFrame: + _previous = _prevFrame; + break; + case PreviousType.LastChange: + if (_prevFrame != value) + { + _previous = _prevFrame; + } + + break; + } + + _prevFrame = value; + } + + public void ClearChangeCount() => ChangeCount = 0; + } + + internal sealed class MiniWordWatchDetailed : IMiniWatch, IMiniWatchDetails + { + public long Address { get; } + + private ushort _previous; + private ushort _prevFrame; + + public MiniWordWatchDetailed(MemoryDomain domain, long addr, bool bigEndian) + { + Address = addr; + SetPreviousToCurrent(domain, bigEndian); + } + + public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) + { + _previous = _prevFrame = domain.PeekUshort(Address % domain.Size, bigEndian); + } + + public long Previous => _previous; + + public int ChangeCount { get; private set; } + + public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) + { + var value = domain.PeekUshort(Address % domain.Size, bigEndian); + if (value != Previous) + { + ChangeCount++; + } + + switch (type) + { + case PreviousType.Original: + case PreviousType.LastSearch: + break; + case PreviousType.LastFrame: + _previous = _prevFrame; + break; + case PreviousType.LastChange: + if (_prevFrame != value) + { + _previous = _prevFrame; + } + + break; + } + + _prevFrame = value; + } + + public void ClearChangeCount() => ChangeCount = 0; + } + + internal sealed class MiniDWordWatchDetailed : IMiniWatch, IMiniWatchDetails + { + public long Address { get; } + + private uint _previous; + private uint _prevFrame; + + public MiniDWordWatchDetailed(MemoryDomain domain, long addr, bool bigEndian) + { + Address = addr; + SetPreviousToCurrent(domain, bigEndian); + } + + public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) + { + _previous = _prevFrame = domain.PeekUint(Address % domain.Size, bigEndian); + } + + public long Previous => (int)_previous; + + public int ChangeCount { get; private set; } + + public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) + { + var value = domain.PeekUint(Address % domain.Size, bigEndian); + if (value != Previous) + { + ChangeCount++; + } + + switch (type) + { + case PreviousType.Original: + case PreviousType.LastSearch: + break; + case PreviousType.LastFrame: + _previous = _prevFrame; + break; + case PreviousType.LastChange: + if (_prevFrame != value) + { + _previous = _prevFrame; + } + + break; + } + + _prevFrame = value; + } + + public void ClearChangeCount() => ChangeCount = 0; + } +} diff --git a/BizHawk.Client.Common/tools/RamSearchEngine.cs b/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs similarity index 79% rename from BizHawk.Client.Common/tools/RamSearchEngine.cs rename to BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs index 0bc8882e40..ca3c041a11 100644 --- a/BizHawk.Client.Common/tools/RamSearchEngine.cs +++ b/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs @@ -1,1059 +1,814 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -using BizHawk.Common; -using BizHawk.Common.CollectionExtensions; -using BizHawk.Emulation.Common; - -// ReSharper disable PossibleInvalidCastExceptionInForeachLoop -namespace BizHawk.Client.Common -{ - public class RamSearchEngine - { - public enum ComparisonOperator - { - Equal, GreaterThan, GreaterThanEqual, LessThan, LessThanEqual, NotEqual, DifferentBy - } - - public enum Compare - { - Previous, SpecificValue, SpecificAddress, Changes, Difference - } - - private Compare _compareTo = Compare.Previous; - - private List _watchList = new List(); - private readonly Settings _settings; - private readonly UndoHistory _history = new UndoHistory(true); - private bool _isSorted = true; // Tracks whether or not the list is sorted by address, if it is, binary search can be used for finding watches - - public RamSearchEngine(Settings settings, IMemoryDomains memoryDomains) - { - _settings = new Settings(memoryDomains) - { - Mode = settings.Mode, - Domain = settings.Domain, - Size = settings.Size, - CheckMisAligned = settings.CheckMisAligned, - Type = settings.Type, - BigEndian = settings.BigEndian, - PreviousType = settings.PreviousType - }; - } - - public RamSearchEngine(Settings settings, IMemoryDomains memoryDomains, Compare compareTo, long? compareValue, int? differentBy) - : this(settings, memoryDomains) - { - _compareTo = compareTo; - DifferentBy = differentBy; - CompareValue = compareValue; - } - - #region API - - public IEnumerable OutOfRangeAddress => _watchList - .Where(watch => watch.Address >= Domain.Size) - .Select(watch => watch.Address); - - public void Start() - { - _history.Clear(); - var domain = _settings.Domain; - var listSize = domain.Size; - if (!_settings.CheckMisAligned) - { - listSize /= (int)_settings.Size; - } - - _watchList = new List((int)listSize); - - switch (_settings.Size) - { - default: - case WatchSize.Byte: - if (_settings.Mode == Settings.SearchMode.Detailed) - { - for (int i = 0; i < domain.Size; i++) - { - _watchList.Add(new MiniByteWatchDetailed(domain, i)); - } - } - else - { - for (int i = 0; i < domain.Size; i++) - { - _watchList.Add(new MiniByteWatch(domain, i)); - } - } - - break; - case WatchSize.Word: - if (_settings.Mode == Settings.SearchMode.Detailed) - { - for (int i = 0; i < domain.Size - 1; i += _settings.CheckMisAligned ? 1 : 2) - { - _watchList.Add(new MiniWordWatchDetailed(domain, i, _settings.BigEndian)); - } - } - else - { - for (int i = 0; i < domain.Size - 1; i += _settings.CheckMisAligned ? 1 : 2) - { - _watchList.Add(new MiniWordWatch(domain, i, _settings.BigEndian)); - } - } - - break; - case WatchSize.DWord: - if (_settings.Mode == Settings.SearchMode.Detailed) - { - for (int i = 0; i < domain.Size - 3; i += _settings.CheckMisAligned ? 1 : 4) - { - _watchList.Add(new MiniDWordWatchDetailed(domain, i, _settings.BigEndian)); - } - } - else - { - for (int i = 0; i < domain.Size - 3; i += _settings.CheckMisAligned ? 1 : 4) - { - _watchList.Add(new MiniDWordWatch(domain, i, _settings.BigEndian)); - } - } - - break; - } - } - - /// - /// Exposes the current watch state based on index - /// - public Watch this[int index] - { - get - { - if (_settings.Mode == Settings.SearchMode.Detailed) - { - return Watch.GenerateWatch( - _settings.Domain, - _watchList[index].Address, - _settings.Size, - _settings.Type, - _settings.BigEndian, - "", - 0, - _watchList[index].Previous, - ((IMiniWatchDetails)_watchList[index]).ChangeCount); - } - - return Watch.GenerateWatch( - _settings.Domain, - _watchList[index].Address, - _settings.Size, - _settings.Type, - _settings.BigEndian, - "", - 0, - _watchList[index].Previous); - } - } - - public int DoSearch() - { - int before = _watchList.Count; - - _watchList = _compareTo switch - { - Compare.Previous => ComparePrevious(_watchList).ToList(), - Compare.SpecificValue => CompareSpecificValue(_watchList).ToList(), - Compare.SpecificAddress => CompareSpecificAddress(_watchList).ToList(), - Compare.Changes => CompareChanges(_watchList).ToList(), - Compare.Difference => CompareDifference(_watchList).ToList(), - _ => ComparePrevious(_watchList).ToList() - }; - - if (_settings.PreviousType == PreviousType.LastSearch) - { - SetPreviousToCurrent(); - } - - if (UndoEnabled) - { - _history.AddState(_watchList); - } - - return before - _watchList.Count; - } - - public bool Preview(long address) - { - var listOfOne = Enumerable.Repeat(_isSorted - ? _watchList.BinarySearch(w => w.Address, address) - : _watchList.FirstOrDefault(w => w.Address == address), 1); - - return _compareTo switch - { - Compare.Previous => !ComparePrevious(listOfOne).Any(), - Compare.SpecificValue => !CompareSpecificValue(listOfOne).Any(), - Compare.SpecificAddress => !CompareSpecificAddress(listOfOne).Any(), - Compare.Changes => !CompareChanges(listOfOne).Any(), - Compare.Difference => !CompareDifference(listOfOne).Any(), - _ => !ComparePrevious(listOfOne).Any() - }; - } - - public int Count => _watchList.Count; - - public Settings.SearchMode Mode => _settings.Mode; - - public MemoryDomain Domain => _settings.Domain; - - /// (from setter) is and is not - public Compare CompareTo - { - get => _compareTo; - - set - { - if (CanDoCompareType(value)) - { - _compareTo = value; - } - else - { - throw new InvalidOperationException(); - } - } - } - - public long? CompareValue { get; set; } - - public ComparisonOperator Operator { get; set; } - - // zero 07-sep-2014 - this isn't ideal. but don't bother changing it (to a long, for instance) until it can support floats. maybe store it as a double here. - public int? DifferentBy { get; set; } - - public void Update() - { - if (_settings.Mode == Settings.SearchMode.Detailed) - { - foreach (IMiniWatchDetails watch in _watchList) - { - watch.Update(_settings.PreviousType, _settings.Domain, _settings.BigEndian); - } - } - } - - public void SetType(DisplayType type) - { - _settings.Type = type; - } - - public void SetEndian(bool bigEndian) - { - _settings.BigEndian = bigEndian; - } - - /// is and is - public void SetPreviousType(PreviousType type) - { - if (_settings.Mode == Settings.SearchMode.Fast) - { - if (type == PreviousType.LastFrame) - { - throw new InvalidOperationException(); - } - } - - _settings.PreviousType = type; - } - - public void SetPreviousToCurrent() - { - _watchList.ForEach(w => w.SetPreviousToCurrent(_settings.Domain, _settings.BigEndian)); - } - - public void ClearChangeCounts() - { - if (_settings.Mode == Settings.SearchMode.Detailed) - { - foreach (var watch in _watchList.Cast()) - { - watch.ClearChangeCount(); - } - } - } - - /// - /// Remove a set of watches - /// However, this should not be used with large data sets (100k or more) as it uses a contains logic to perform the task - /// - public void RemoveSmallWatchRange(IEnumerable watches) - { - if (UndoEnabled) - { - _history.AddState(_watchList); - } - - var addresses = watches.Select(w => w.Address); - _watchList.RemoveAll(w => addresses.Contains(w.Address)); - } - - public void RemoveRange(IEnumerable indices) - { - if (UndoEnabled) - { - _history.AddState(_watchList); - } - - var removeList = indices.Select(i => _watchList[i]); // This will fail after int.MaxValue but RAM Search fails on domains that large anyway - _watchList = _watchList.Except(removeList).ToList(); - } - - public void AddRange(List addresses, bool append) - { - if (!append) - { - _watchList.Clear(); - } - - switch (_settings.Size) - { - default: - case WatchSize.Byte: - if (_settings.Mode == Settings.SearchMode.Detailed) - { - foreach (var addr in addresses) - { - _watchList.Add(new MiniByteWatchDetailed(_settings.Domain, addr)); - } - } - else - { - foreach (var addr in addresses) - { - _watchList.Add(new MiniByteWatch(_settings.Domain, addr)); - } - } - - break; - case WatchSize.Word: - if (_settings.Mode == Settings.SearchMode.Detailed) - { - foreach (var addr in addresses) - { - _watchList.Add(new MiniWordWatchDetailed(_settings.Domain, addr, _settings.BigEndian)); - } - } - else - { - foreach (var addr in addresses) - { - _watchList.Add(new MiniWordWatch(_settings.Domain, addr, _settings.BigEndian)); - } - } - - break; - case WatchSize.DWord: - if (_settings.Mode == Settings.SearchMode.Detailed) - { - foreach (var addr in addresses) - { - _watchList.Add(new MiniDWordWatchDetailed(_settings.Domain, addr, _settings.BigEndian)); - } - } - else - { - foreach (var addr in addresses) - { - _watchList.Add(new MiniDWordWatch(_settings.Domain, addr, _settings.BigEndian)); - } - } - - break; - } - } - - public void Sort(string column, bool reverse) - { - _isSorted = false; - switch (column) - { - case WatchList.ADDRESS: - if (reverse) - { - _watchList = _watchList.OrderByDescending(w => w.Address).ToList(); - } - else - { - _watchList = _watchList.OrderBy(w => w.Address).ToList(); - _isSorted = true; - } - - break; - case WatchList.VALUE: - _watchList = reverse - ? _watchList.OrderByDescending(w => GetValue(w.Address)).ToList() - : _watchList.OrderBy(w => GetValue(w.Address)).ToList(); - - break; - case WatchList.PREV: - _watchList = reverse - ? _watchList.OrderByDescending(w => w.Previous).ToList() - : _watchList.OrderBy(w => w.Previous).ToList(); - - break; - case WatchList.CHANGES: - if (_settings.Mode == Settings.SearchMode.Detailed) - { - if (reverse) - { - _watchList = _watchList - .Cast() - .OrderByDescending(w => w.ChangeCount) - .Cast().ToList(); - } - else - { - _watchList = _watchList - .Cast() - .OrderBy(w => w.ChangeCount) - .Cast().ToList(); - } - } - - break; - case WatchList.DIFF: - _watchList = reverse - ? _watchList.OrderByDescending(w => GetValue(w.Address) - w.Previous).ToList() - : _watchList.OrderBy(w => GetValue(w.Address) - w.Previous).ToList(); - - break; - } - } - - #endregion - - #region Undo API - - public bool UndoEnabled { get; set; } - - - public bool CanUndo => UndoEnabled && _history.CanUndo; - - public bool CanRedo => UndoEnabled && _history.CanRedo; - - public void ClearHistory() - { - _history.Clear(); - } - - public int Undo() - { - int origCount = _watchList.Count; - if (UndoEnabled) - { - _watchList = _history.Undo().ToList(); - return _watchList.Count - origCount; - } - - return _watchList.Count; - } - - public int Redo() - { - int origCount = _watchList.Count; - if (UndoEnabled) - { - _watchList = _history.Redo().ToList(); - return origCount - _watchList.Count; - } - - return _watchList.Count; - } - - #endregion - - #region Comparisons - - private IEnumerable ComparePrevious(IEnumerable watchList) - { - switch (Operator) - { - default: - case ComparisonOperator.Equal: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) == ToFloat(w.Previous)) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) == SignExtendAsNeeded(w.Previous)); - case ComparisonOperator.NotEqual: - return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) != SignExtendAsNeeded(w.Previous)); - case ComparisonOperator.GreaterThan: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) > ToFloat(w.Previous)) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) > SignExtendAsNeeded(w.Previous)); - case ComparisonOperator.GreaterThanEqual: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) >= ToFloat(w.Previous)) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) >= SignExtendAsNeeded(w.Previous)); - - case ComparisonOperator.LessThan: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) < ToFloat(w.Previous)) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) < SignExtendAsNeeded(w.Previous)); - case ComparisonOperator.LessThanEqual: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) <= ToFloat(w.Previous)) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) <= SignExtendAsNeeded(w.Previous)); - case ComparisonOperator.DifferentBy: - if (DifferentBy.HasValue) - { - var differentBy = DifferentBy.Value; - if (_settings.Type == DisplayType.Float) - { - return watchList.Where(w => ToFloat(GetValue(w.Address)) + differentBy == ToFloat(w.Previous) - || ToFloat(GetValue(w.Address)) - differentBy == ToFloat(w.Previous)); - } - - return watchList.Where(w => - { - long val = SignExtendAsNeeded(GetValue(w.Address)); - long prev = SignExtendAsNeeded(w.Previous); - return val + differentBy == prev - || val - differentBy == prev; - }); - } - else - { - throw new InvalidOperationException(); - } - } - } - - private IEnumerable CompareSpecificValue(IEnumerable watchList) - { - if (CompareValue.HasValue) - { - var compareValue = CompareValue.Value; - switch (Operator) - { - default: - case ComparisonOperator.Equal: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) == ToFloat(compareValue)) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) == SignExtendAsNeeded(CompareValue.Value)); - case ComparisonOperator.NotEqual: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) != ToFloat(compareValue)) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) != SignExtendAsNeeded(compareValue)); - case ComparisonOperator.GreaterThan: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) > ToFloat(compareValue)) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) > SignExtendAsNeeded(compareValue)); - case ComparisonOperator.GreaterThanEqual: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) >= ToFloat(compareValue)) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) >= SignExtendAsNeeded(compareValue)); - case ComparisonOperator.LessThan: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) < ToFloat(compareValue)) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) < SignExtendAsNeeded(compareValue)); - - case ComparisonOperator.LessThanEqual: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) <= ToFloat(compareValue)) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) <= SignExtendAsNeeded(compareValue)); - - case ComparisonOperator.DifferentBy: - if (DifferentBy.HasValue) - { - var differentBy = DifferentBy.Value; - if (_settings.Type == DisplayType.Float) - { - return watchList.Where(w => ToFloat(GetValue(w.Address)) + differentBy == compareValue - || ToFloat(GetValue(w.Address)) - differentBy == compareValue); - } - - return watchList.Where(w - => SignExtendAsNeeded(GetValue(w.Address)) + differentBy == compareValue - || SignExtendAsNeeded(GetValue(w.Address)) - differentBy == compareValue); - } - - throw new InvalidOperationException(); - } - } - - throw new InvalidOperationException(); - } - - private IEnumerable CompareSpecificAddress(IEnumerable watchList) - { - if (CompareValue.HasValue) - { - var compareValue = CompareValue.Value; - switch (Operator) - { - default: - case ComparisonOperator.Equal: - return watchList.Where(w => w.Address == compareValue); - case ComparisonOperator.NotEqual: - return watchList.Where(w => w.Address != compareValue); - case ComparisonOperator.GreaterThan: - return watchList.Where(w => w.Address > compareValue); - case ComparisonOperator.GreaterThanEqual: - return watchList.Where(w => w.Address >= compareValue); - case ComparisonOperator.LessThan: - return watchList.Where(w => w.Address < compareValue); - case ComparisonOperator.LessThanEqual: - return watchList.Where(w => w.Address <= compareValue); - case ComparisonOperator.DifferentBy: - if (DifferentBy.HasValue) - { - return watchList.Where(w => w.Address + DifferentBy.Value == compareValue - || w.Address - DifferentBy.Value == compareValue); - } - - throw new InvalidOperationException(); - } - } - - throw new InvalidOperationException(); - } - - private IEnumerable CompareChanges(IEnumerable watchList) - { - if (_settings.Mode == Settings.SearchMode.Detailed && CompareValue.HasValue) - { - var compareValue = CompareValue.Value; - switch (Operator) - { - default: - case ComparisonOperator.Equal: - return watchList - .Cast() - .Where(w => w.ChangeCount == compareValue) - .Cast(); - case ComparisonOperator.NotEqual: - return watchList - .Cast() - .Where(w => w.ChangeCount != compareValue) - .Cast(); - case ComparisonOperator.GreaterThan: - return watchList - .Cast() - .Where(w => w.ChangeCount > compareValue) - .Cast(); - case ComparisonOperator.GreaterThanEqual: - return watchList - .Cast() - .Where(w => w.ChangeCount >= compareValue) - .Cast(); - case ComparisonOperator.LessThan: - return watchList - .Cast() - .Where(w => w.ChangeCount < compareValue) - .Cast(); - case ComparisonOperator.LessThanEqual: - return watchList - .Cast() - .Where(w => w.ChangeCount <= compareValue) - .Cast(); - case ComparisonOperator.DifferentBy: - if (DifferentBy.HasValue) - { - return watchList - .Cast() - .Where(w => w.ChangeCount + DifferentBy.Value == compareValue - || w.ChangeCount - DifferentBy.Value == compareValue) - .Cast(); - } - - throw new InvalidOperationException(); - } - } - - throw new InvalidCastException(); - } - - private IEnumerable CompareDifference(IEnumerable watchList) - { - if (CompareValue.HasValue) - { - var compareValue = CompareValue.Value; - switch (Operator) - { - default: - case ComparisonOperator.Equal: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) - ToFloat(w.Previous) == compareValue) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) == compareValue); - case ComparisonOperator.NotEqual: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) - w.Previous != compareValue) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) != compareValue); - case ComparisonOperator.GreaterThan: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) - w.Previous > compareValue) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) > compareValue); - case ComparisonOperator.GreaterThanEqual: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) - w.Previous >= compareValue) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) >= compareValue); - case ComparisonOperator.LessThan: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) - w.Previous < compareValue) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) < compareValue); - case ComparisonOperator.LessThanEqual: - return _settings.Type == DisplayType.Float - ? watchList.Where(w => ToFloat(GetValue(w.Address)) - w.Previous <= compareValue) - : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) <= compareValue); - case ComparisonOperator.DifferentBy: - if (DifferentBy.HasValue) - { - var differentBy = DifferentBy.Value; - if (_settings.Type == DisplayType.Float) - { - return watchList.Where(w => ToFloat(GetValue(w.Address)) - w.Previous + differentBy == compareValue - || ToFloat(GetValue(w.Address)) - w.Previous - differentBy == w.Previous); - } - - return watchList.Where(w - => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) + differentBy == compareValue - || SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) - differentBy == compareValue); - } - - throw new InvalidOperationException(); - } - } - - throw new InvalidCastException(); - } - - #endregion - - #region Private parts - - private static float ToFloat(long val) - { - var bytes = BitConverter.GetBytes((int)val); - return BitConverter.ToSingle(bytes, 0); - } - - private long SignExtendAsNeeded(long val) - { - if (_settings.Type != DisplayType.Signed) - { - return val; - } - - return _settings.Size switch - { - WatchSize.Byte => (sbyte) val, - WatchSize.Word => (short) val, - WatchSize.DWord => (int) val, - _ => (sbyte) val - }; - } - - private long GetValue(long addr) - { - // do not return sign extended variables from here. - return _settings.Size switch - { - WatchSize.Byte => _settings.Domain.PeekByte(addr % Domain.Size), - WatchSize.Word => _settings.Domain.PeekUshort(addr % Domain.Size, _settings.BigEndian), - WatchSize.DWord => _settings.Domain.PeekUint(addr % Domain.Size, _settings.BigEndian), - _ => _settings.Domain.PeekByte(addr % Domain.Size) - }; - } - - private bool CanDoCompareType(Compare compareType) - { - return _settings.Mode switch - { - Settings.SearchMode.Detailed => true, - Settings.SearchMode.Fast => (compareType != Compare.Changes), - _ => true - }; - } - - #endregion - - #region Classes - - private interface IMiniWatch - { - long Address { get; } - long Previous { get; } // do not store sign extended variables in here. - void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian); - } - - private interface IMiniWatchDetails - { - int ChangeCount { get; } - - void ClearChangeCount(); - void Update(PreviousType type, MemoryDomain domain, bool bigEndian); - } - - private sealed class MiniByteWatch : IMiniWatch - { - public long Address { get; } - private byte _previous; - - public MiniByteWatch(MemoryDomain domain, long addr) - { - Address = addr; - _previous = domain.PeekByte(Address % domain.Size); - } - - public long Previous => _previous; - - public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) - { - _previous = domain.PeekByte(Address % domain.Size); - } - } - - private sealed class MiniWordWatch : IMiniWatch - { - public long Address { get; } - private ushort _previous; - - public MiniWordWatch(MemoryDomain domain, long addr, bool bigEndian) - { - Address = addr; - _previous = domain.PeekUshort(Address % domain.Size, bigEndian); - } - - public long Previous => _previous; - - public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) - { - _previous = domain.PeekUshort(Address, bigEndian); - } - } - - private sealed class MiniDWordWatch : IMiniWatch - { - public long Address { get; } - private uint _previous; - - public MiniDWordWatch(MemoryDomain domain, long addr, bool bigEndian) - { - Address = addr; - _previous = domain.PeekUint(Address % domain.Size, bigEndian); - } - - public long Previous => _previous; - - public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) - { - _previous = domain.PeekUint(Address, bigEndian); - } - } - - private sealed class MiniByteWatchDetailed : IMiniWatch, IMiniWatchDetails - { - public long Address { get; } - - private byte _previous; - private byte _prevFrame; - - public MiniByteWatchDetailed(MemoryDomain domain, long addr) - { - Address = addr; - SetPreviousToCurrent(domain, false); - } - - public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) - { - _previous = _prevFrame = domain.PeekByte(Address % domain.Size); - } - - public long Previous => _previous; - - public int ChangeCount { get; private set; } - - public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) - { - var value = domain.PeekByte(Address % domain.Size); - - if (value != _prevFrame) - { - ChangeCount++; - } - - switch (type) - { - case PreviousType.Original: - case PreviousType.LastSearch: - break; - case PreviousType.LastFrame: - _previous = _prevFrame; - break; - case PreviousType.LastChange: - if (_prevFrame != value) - { - _previous = _prevFrame; - } - - break; - } - - _prevFrame = value; - } - - public void ClearChangeCount() - { - ChangeCount = 0; - } - } - - private sealed class MiniWordWatchDetailed : IMiniWatch, IMiniWatchDetails - { - public long Address { get; } - - private ushort _previous; - private ushort _prevFrame; - - public MiniWordWatchDetailed(MemoryDomain domain, long addr, bool bigEndian) - { - Address = addr; - SetPreviousToCurrent(domain, bigEndian); - } - - public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) - { - _previous = _prevFrame = domain.PeekUshort(Address % domain.Size, bigEndian); - } - - public long Previous => _previous; - - public int ChangeCount { get; private set; } - - public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) - { - var value = domain.PeekUshort(Address % domain.Size, bigEndian); - if (value != Previous) - { - ChangeCount++; - } - - switch (type) - { - case PreviousType.Original: - case PreviousType.LastSearch: - break; - case PreviousType.LastFrame: - _previous = _prevFrame; - break; - case PreviousType.LastChange: - if (_prevFrame != value) - { - _previous = _prevFrame; - } - - break; - } - - _prevFrame = value; - } - - public void ClearChangeCount() - { - ChangeCount = 0; - } - } - - private sealed class MiniDWordWatchDetailed : IMiniWatch, IMiniWatchDetails - { - public long Address { get; } - - private uint _previous; - private uint _prevFrame; - - public MiniDWordWatchDetailed(MemoryDomain domain, long addr, bool bigEndian) - { - Address = addr; - SetPreviousToCurrent(domain, bigEndian); - } - - public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) - { - _previous = _prevFrame = domain.PeekUint(Address % domain.Size, bigEndian); - } - - public long Previous => (int)_previous; - - public int ChangeCount { get; private set; } - - public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) - { - var value = domain.PeekUint(Address % domain.Size, bigEndian); - if (value != Previous) - { - ChangeCount++; - } - - switch (type) - { - case PreviousType.Original: - case PreviousType.LastSearch: - break; - case PreviousType.LastFrame: - _previous = _prevFrame; - break; - case PreviousType.LastChange: - if (_prevFrame != value) - { - _previous = _prevFrame; - } - - break; - } - - _prevFrame = value; - } - - public void ClearChangeCount() - { - ChangeCount = 0; - } - } - - public class Settings - { - public Settings(IMemoryDomains memoryDomains) - { - BigEndian = memoryDomains.MainMemory.EndianType == MemoryDomain.Endian.Big; - Size = (WatchSize)memoryDomains.MainMemory.WordSize; - Type = DisplayType.Unsigned; - Mode = memoryDomains.MainMemory.Size > 1024 * 1024 - ? SearchMode.Fast - : SearchMode.Detailed; - - Domain = memoryDomains.MainMemory; - CheckMisAligned = false; - PreviousType = PreviousType.LastSearch; - } - - /*Require restart*/ - public enum SearchMode - { - Fast, Detailed - } - - public SearchMode Mode { get; set; } - public MemoryDomain Domain { get; set; } - public WatchSize Size { get; set; } - public bool CheckMisAligned { get; set; } - - /*Can be changed mid-search*/ - public DisplayType Type { get; set; } - public bool BigEndian { get; set; } - public PreviousType PreviousType { get; set; } - } - - #endregion - } -} +using System; +using System.Collections.Generic; +using System.Linq; + +using BizHawk.Common; +using BizHawk.Common.CollectionExtensions; +using BizHawk.Emulation.Common; + +// ReSharper disable PossibleInvalidCastExceptionInForeachLoop +namespace BizHawk.Client.Common +{ + public class RamSearchEngine + { + public enum ComparisonOperator + { + Equal, GreaterThan, GreaterThanEqual, LessThan, LessThanEqual, NotEqual, DifferentBy + } + + public enum Compare + { + Previous, SpecificValue, SpecificAddress, Changes, Difference + } + + private Compare _compareTo = Compare.Previous; + + private List _watchList = new List(); + private readonly Settings _settings; + private readonly UndoHistory _history = new UndoHistory(true); + private bool _isSorted = true; // Tracks whether or not the list is sorted by address, if it is, binary search can be used for finding watches + + public RamSearchEngine(Settings settings, IMemoryDomains memoryDomains) + { + _settings = new Settings(memoryDomains) + { + Mode = settings.Mode, + Domain = settings.Domain, + Size = settings.Size, + CheckMisAligned = settings.CheckMisAligned, + Type = settings.Type, + BigEndian = settings.BigEndian, + PreviousType = settings.PreviousType + }; + } + + public RamSearchEngine(Settings settings, IMemoryDomains memoryDomains, Compare compareTo, long? compareValue, int? differentBy) + : this(settings, memoryDomains) + { + _compareTo = compareTo; + DifferentBy = differentBy; + CompareValue = compareValue; + } + + #region API + + public IEnumerable OutOfRangeAddress => _watchList + .Where(watch => watch.Address >= Domain.Size) + .Select(watch => watch.Address); + + public void Start() + { + _history.Clear(); + var domain = _settings.Domain; + var listSize = domain.Size; + if (!_settings.CheckMisAligned) + { + listSize /= (int)_settings.Size; + } + + _watchList = new List((int)listSize); + + switch (_settings.Size) + { + default: + case WatchSize.Byte: + if (_settings.Mode == Settings.SearchMode.Detailed) + { + for (int i = 0; i < domain.Size; i++) + { + _watchList.Add(new MiniByteWatchDetailed(domain, i)); + } + } + else + { + for (int i = 0; i < domain.Size; i++) + { + _watchList.Add(new MiniByteWatch(domain, i)); + } + } + + break; + case WatchSize.Word: + if (_settings.Mode == Settings.SearchMode.Detailed) + { + for (int i = 0; i < domain.Size - 1; i += _settings.CheckMisAligned ? 1 : 2) + { + _watchList.Add(new MiniWordWatchDetailed(domain, i, _settings.BigEndian)); + } + } + else + { + for (int i = 0; i < domain.Size - 1; i += _settings.CheckMisAligned ? 1 : 2) + { + _watchList.Add(new MiniWordWatch(domain, i, _settings.BigEndian)); + } + } + + break; + case WatchSize.DWord: + if (_settings.Mode == Settings.SearchMode.Detailed) + { + for (int i = 0; i < domain.Size - 3; i += _settings.CheckMisAligned ? 1 : 4) + { + _watchList.Add(new MiniDWordWatchDetailed(domain, i, _settings.BigEndian)); + } + } + else + { + for (int i = 0; i < domain.Size - 3; i += _settings.CheckMisAligned ? 1 : 4) + { + _watchList.Add(new MiniDWordWatch(domain, i, _settings.BigEndian)); + } + } + + break; + } + } + + /// + /// Exposes the current watch state based on index + /// + public Watch this[int index] + { + get + { + if (_settings.Mode == Settings.SearchMode.Detailed) + { + return Watch.GenerateWatch( + _settings.Domain, + _watchList[index].Address, + _settings.Size, + _settings.Type, + _settings.BigEndian, + "", + 0, + _watchList[index].Previous, + ((IMiniWatchDetails)_watchList[index]).ChangeCount); + } + + return Watch.GenerateWatch( + _settings.Domain, + _watchList[index].Address, + _settings.Size, + _settings.Type, + _settings.BigEndian, + "", + 0, + _watchList[index].Previous); + } + } + + public int DoSearch() + { + int before = _watchList.Count; + + _watchList = _compareTo switch + { + Compare.Previous => ComparePrevious(_watchList).ToList(), + Compare.SpecificValue => CompareSpecificValue(_watchList).ToList(), + Compare.SpecificAddress => CompareSpecificAddress(_watchList).ToList(), + Compare.Changes => CompareChanges(_watchList).ToList(), + Compare.Difference => CompareDifference(_watchList).ToList(), + _ => ComparePrevious(_watchList).ToList() + }; + + if (_settings.PreviousType == PreviousType.LastSearch) + { + SetPreviousToCurrent(); + } + + if (UndoEnabled) + { + _history.AddState(_watchList); + } + + return before - _watchList.Count; + } + + public bool Preview(long address) + { + var listOfOne = Enumerable.Repeat(_isSorted + ? _watchList.BinarySearch(w => w.Address, address) + : _watchList.FirstOrDefault(w => w.Address == address), 1); + + return _compareTo switch + { + Compare.Previous => !ComparePrevious(listOfOne).Any(), + Compare.SpecificValue => !CompareSpecificValue(listOfOne).Any(), + Compare.SpecificAddress => !CompareSpecificAddress(listOfOne).Any(), + Compare.Changes => !CompareChanges(listOfOne).Any(), + Compare.Difference => !CompareDifference(listOfOne).Any(), + _ => !ComparePrevious(listOfOne).Any() + }; + } + + public int Count => _watchList.Count; + + public Settings.SearchMode Mode => _settings.Mode; + + public MemoryDomain Domain => _settings.Domain; + + /// (from setter) is and is not + public Compare CompareTo + { + get => _compareTo; + + set + { + if (CanDoCompareType(value)) + { + _compareTo = value; + } + else + { + throw new InvalidOperationException(); + } + } + } + + public long? CompareValue { get; set; } + + public ComparisonOperator Operator { get; set; } + + // zero 07-sep-2014 - this isn't ideal. but don't bother changing it (to a long, for instance) until it can support floats. maybe store it as a double here. + public int? DifferentBy { get; set; } + + public void Update() + { + if (_settings.Mode == Settings.SearchMode.Detailed) + { + foreach (IMiniWatchDetails watch in _watchList) + { + watch.Update(_settings.PreviousType, _settings.Domain, _settings.BigEndian); + } + } + } + + public void SetType(DisplayType type) + { + _settings.Type = type; + } + + public void SetEndian(bool bigEndian) + { + _settings.BigEndian = bigEndian; + } + + /// is and is + public void SetPreviousType(PreviousType type) + { + if (_settings.Mode == Settings.SearchMode.Fast) + { + if (type == PreviousType.LastFrame) + { + throw new InvalidOperationException(); + } + } + + _settings.PreviousType = type; + } + + public void SetPreviousToCurrent() + { + _watchList.ForEach(w => w.SetPreviousToCurrent(_settings.Domain, _settings.BigEndian)); + } + + public void ClearChangeCounts() + { + if (_settings.Mode == Settings.SearchMode.Detailed) + { + foreach (var watch in _watchList.Cast()) + { + watch.ClearChangeCount(); + } + } + } + + /// + /// Remove a set of watches + /// However, this should not be used with large data sets (100k or more) as it uses a contains logic to perform the task + /// + public void RemoveSmallWatchRange(IEnumerable watches) + { + if (UndoEnabled) + { + _history.AddState(_watchList); + } + + var addresses = watches.Select(w => w.Address); + _watchList.RemoveAll(w => addresses.Contains(w.Address)); + } + + public void RemoveRange(IEnumerable indices) + { + if (UndoEnabled) + { + _history.AddState(_watchList); + } + + var removeList = indices.Select(i => _watchList[i]); // This will fail after int.MaxValue but RAM Search fails on domains that large anyway + _watchList = _watchList.Except(removeList).ToList(); + } + + public void AddRange(List addresses, bool append) + { + if (!append) + { + _watchList.Clear(); + } + + switch (_settings.Size) + { + default: + case WatchSize.Byte: + if (_settings.Mode == Settings.SearchMode.Detailed) + { + foreach (var addr in addresses) + { + _watchList.Add(new MiniByteWatchDetailed(_settings.Domain, addr)); + } + } + else + { + foreach (var addr in addresses) + { + _watchList.Add(new MiniByteWatch(_settings.Domain, addr)); + } + } + + break; + case WatchSize.Word: + if (_settings.Mode == Settings.SearchMode.Detailed) + { + foreach (var addr in addresses) + { + _watchList.Add(new MiniWordWatchDetailed(_settings.Domain, addr, _settings.BigEndian)); + } + } + else + { + foreach (var addr in addresses) + { + _watchList.Add(new MiniWordWatch(_settings.Domain, addr, _settings.BigEndian)); + } + } + + break; + case WatchSize.DWord: + if (_settings.Mode == Settings.SearchMode.Detailed) + { + foreach (var addr in addresses) + { + _watchList.Add(new MiniDWordWatchDetailed(_settings.Domain, addr, _settings.BigEndian)); + } + } + else + { + foreach (var addr in addresses) + { + _watchList.Add(new MiniDWordWatch(_settings.Domain, addr, _settings.BigEndian)); + } + } + + break; + } + } + + public void Sort(string column, bool reverse) + { + _isSorted = false; + switch (column) + { + case WatchList.ADDRESS: + if (reverse) + { + _watchList = _watchList.OrderByDescending(w => w.Address).ToList(); + } + else + { + _watchList = _watchList.OrderBy(w => w.Address).ToList(); + _isSorted = true; + } + + break; + case WatchList.VALUE: + _watchList = reverse + ? _watchList.OrderByDescending(w => GetValue(w.Address)).ToList() + : _watchList.OrderBy(w => GetValue(w.Address)).ToList(); + + break; + case WatchList.PREV: + _watchList = reverse + ? _watchList.OrderByDescending(w => w.Previous).ToList() + : _watchList.OrderBy(w => w.Previous).ToList(); + + break; + case WatchList.CHANGES: + if (_settings.Mode == Settings.SearchMode.Detailed) + { + if (reverse) + { + _watchList = _watchList + .Cast() + .OrderByDescending(w => w.ChangeCount) + .Cast().ToList(); + } + else + { + _watchList = _watchList + .Cast() + .OrderBy(w => w.ChangeCount) + .Cast().ToList(); + } + } + + break; + case WatchList.DIFF: + _watchList = reverse + ? _watchList.OrderByDescending(w => GetValue(w.Address) - w.Previous).ToList() + : _watchList.OrderBy(w => GetValue(w.Address) - w.Previous).ToList(); + + break; + } + } + + #endregion + + #region Undo API + + public bool UndoEnabled { get; set; } + + + public bool CanUndo => UndoEnabled && _history.CanUndo; + + public bool CanRedo => UndoEnabled && _history.CanRedo; + + public void ClearHistory() + { + _history.Clear(); + } + + public int Undo() + { + int origCount = _watchList.Count; + if (UndoEnabled) + { + _watchList = _history.Undo().ToList(); + return _watchList.Count - origCount; + } + + return _watchList.Count; + } + + public int Redo() + { + int origCount = _watchList.Count; + if (UndoEnabled) + { + _watchList = _history.Redo().ToList(); + return origCount - _watchList.Count; + } + + return _watchList.Count; + } + + #endregion + + #region Comparisons + + private IEnumerable ComparePrevious(IEnumerable watchList) + { + switch (Operator) + { + default: + case ComparisonOperator.Equal: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) == ToFloat(w.Previous)) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) == SignExtendAsNeeded(w.Previous)); + case ComparisonOperator.NotEqual: + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) != SignExtendAsNeeded(w.Previous)); + case ComparisonOperator.GreaterThan: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) > ToFloat(w.Previous)) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) > SignExtendAsNeeded(w.Previous)); + case ComparisonOperator.GreaterThanEqual: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) >= ToFloat(w.Previous)) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) >= SignExtendAsNeeded(w.Previous)); + + case ComparisonOperator.LessThan: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) < ToFloat(w.Previous)) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) < SignExtendAsNeeded(w.Previous)); + case ComparisonOperator.LessThanEqual: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) <= ToFloat(w.Previous)) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) <= SignExtendAsNeeded(w.Previous)); + case ComparisonOperator.DifferentBy: + if (DifferentBy.HasValue) + { + var differentBy = DifferentBy.Value; + if (_settings.Type == DisplayType.Float) + { + return watchList.Where(w => ToFloat(GetValue(w.Address)) + differentBy == ToFloat(w.Previous) + || ToFloat(GetValue(w.Address)) - differentBy == ToFloat(w.Previous)); + } + + return watchList.Where(w => + { + long val = SignExtendAsNeeded(GetValue(w.Address)); + long prev = SignExtendAsNeeded(w.Previous); + return val + differentBy == prev + || val - differentBy == prev; + }); + } + else + { + throw new InvalidOperationException(); + } + } + } + + private IEnumerable CompareSpecificValue(IEnumerable watchList) + { + if (CompareValue.HasValue) + { + var compareValue = CompareValue.Value; + switch (Operator) + { + default: + case ComparisonOperator.Equal: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) == ToFloat(compareValue)) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) == SignExtendAsNeeded(CompareValue.Value)); + case ComparisonOperator.NotEqual: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) != ToFloat(compareValue)) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) != SignExtendAsNeeded(compareValue)); + case ComparisonOperator.GreaterThan: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) > ToFloat(compareValue)) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) > SignExtendAsNeeded(compareValue)); + case ComparisonOperator.GreaterThanEqual: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) >= ToFloat(compareValue)) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) >= SignExtendAsNeeded(compareValue)); + case ComparisonOperator.LessThan: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) < ToFloat(compareValue)) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) < SignExtendAsNeeded(compareValue)); + + case ComparisonOperator.LessThanEqual: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) <= ToFloat(compareValue)) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) <= SignExtendAsNeeded(compareValue)); + + case ComparisonOperator.DifferentBy: + if (DifferentBy.HasValue) + { + var differentBy = DifferentBy.Value; + if (_settings.Type == DisplayType.Float) + { + return watchList.Where(w => ToFloat(GetValue(w.Address)) + differentBy == compareValue + || ToFloat(GetValue(w.Address)) - differentBy == compareValue); + } + + return watchList.Where(w + => SignExtendAsNeeded(GetValue(w.Address)) + differentBy == compareValue + || SignExtendAsNeeded(GetValue(w.Address)) - differentBy == compareValue); + } + + throw new InvalidOperationException(); + } + } + + throw new InvalidOperationException(); + } + + private IEnumerable CompareSpecificAddress(IEnumerable watchList) + { + if (CompareValue.HasValue) + { + var compareValue = CompareValue.Value; + switch (Operator) + { + default: + case ComparisonOperator.Equal: + return watchList.Where(w => w.Address == compareValue); + case ComparisonOperator.NotEqual: + return watchList.Where(w => w.Address != compareValue); + case ComparisonOperator.GreaterThan: + return watchList.Where(w => w.Address > compareValue); + case ComparisonOperator.GreaterThanEqual: + return watchList.Where(w => w.Address >= compareValue); + case ComparisonOperator.LessThan: + return watchList.Where(w => w.Address < compareValue); + case ComparisonOperator.LessThanEqual: + return watchList.Where(w => w.Address <= compareValue); + case ComparisonOperator.DifferentBy: + if (DifferentBy.HasValue) + { + return watchList.Where(w => w.Address + DifferentBy.Value == compareValue + || w.Address - DifferentBy.Value == compareValue); + } + + throw new InvalidOperationException(); + } + } + + throw new InvalidOperationException(); + } + + private IEnumerable CompareChanges(IEnumerable watchList) + { + if (_settings.Mode == Settings.SearchMode.Detailed && CompareValue.HasValue) + { + var compareValue = CompareValue.Value; + switch (Operator) + { + default: + case ComparisonOperator.Equal: + return watchList + .Cast() + .Where(w => w.ChangeCount == compareValue) + .Cast(); + case ComparisonOperator.NotEqual: + return watchList + .Cast() + .Where(w => w.ChangeCount != compareValue) + .Cast(); + case ComparisonOperator.GreaterThan: + return watchList + .Cast() + .Where(w => w.ChangeCount > compareValue) + .Cast(); + case ComparisonOperator.GreaterThanEqual: + return watchList + .Cast() + .Where(w => w.ChangeCount >= compareValue) + .Cast(); + case ComparisonOperator.LessThan: + return watchList + .Cast() + .Where(w => w.ChangeCount < compareValue) + .Cast(); + case ComparisonOperator.LessThanEqual: + return watchList + .Cast() + .Where(w => w.ChangeCount <= compareValue) + .Cast(); + case ComparisonOperator.DifferentBy: + if (DifferentBy.HasValue) + { + return watchList + .Cast() + .Where(w => w.ChangeCount + DifferentBy.Value == compareValue + || w.ChangeCount - DifferentBy.Value == compareValue) + .Cast(); + } + + throw new InvalidOperationException(); + } + } + + throw new InvalidCastException(); + } + + private IEnumerable CompareDifference(IEnumerable watchList) + { + if (CompareValue.HasValue) + { + var compareValue = CompareValue.Value; + switch (Operator) + { + default: + case ComparisonOperator.Equal: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) - ToFloat(w.Previous) == compareValue) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) == compareValue); + case ComparisonOperator.NotEqual: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) - w.Previous != compareValue) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) != compareValue); + case ComparisonOperator.GreaterThan: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) - w.Previous > compareValue) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) > compareValue); + case ComparisonOperator.GreaterThanEqual: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) - w.Previous >= compareValue) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) >= compareValue); + case ComparisonOperator.LessThan: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) - w.Previous < compareValue) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) < compareValue); + case ComparisonOperator.LessThanEqual: + return _settings.Type == DisplayType.Float + ? watchList.Where(w => ToFloat(GetValue(w.Address)) - w.Previous <= compareValue) + : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) <= compareValue); + case ComparisonOperator.DifferentBy: + if (DifferentBy.HasValue) + { + var differentBy = DifferentBy.Value; + if (_settings.Type == DisplayType.Float) + { + return watchList.Where(w => ToFloat(GetValue(w.Address)) - w.Previous + differentBy == compareValue + || ToFloat(GetValue(w.Address)) - w.Previous - differentBy == w.Previous); + } + + return watchList.Where(w + => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) + differentBy == compareValue + || SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) - differentBy == compareValue); + } + + throw new InvalidOperationException(); + } + } + + throw new InvalidCastException(); + } + + #endregion + + #region Private parts + + private static float ToFloat(long val) + { + var bytes = BitConverter.GetBytes((int)val); + return BitConverter.ToSingle(bytes, 0); + } + + private long SignExtendAsNeeded(long val) + { + if (_settings.Type != DisplayType.Signed) + { + return val; + } + + return _settings.Size switch + { + WatchSize.Byte => (sbyte) val, + WatchSize.Word => (short) val, + WatchSize.DWord => (int) val, + _ => (sbyte) val + }; + } + + private long GetValue(long addr) + { + // do not return sign extended variables from here. + return _settings.Size switch + { + WatchSize.Byte => _settings.Domain.PeekByte(addr % Domain.Size), + WatchSize.Word => _settings.Domain.PeekUshort(addr % Domain.Size, _settings.BigEndian), + WatchSize.DWord => _settings.Domain.PeekUint(addr % Domain.Size, _settings.BigEndian), + _ => _settings.Domain.PeekByte(addr % Domain.Size) + }; + } + + private bool CanDoCompareType(Compare compareType) + { + return _settings.Mode switch + { + Settings.SearchMode.Detailed => true, + Settings.SearchMode.Fast => (compareType != Compare.Changes), + _ => true + }; + } + + #endregion + + public class Settings + { + public Settings(IMemoryDomains memoryDomains) + { + BigEndian = memoryDomains.MainMemory.EndianType == MemoryDomain.Endian.Big; + Size = (WatchSize)memoryDomains.MainMemory.WordSize; + Type = DisplayType.Unsigned; + Mode = memoryDomains.MainMemory.Size > 1024 * 1024 + ? SearchMode.Fast + : SearchMode.Detailed; + + Domain = memoryDomains.MainMemory; + CheckMisAligned = false; + PreviousType = PreviousType.LastSearch; + } + + /*Require restart*/ + public enum SearchMode + { + Fast, Detailed + } + + public SearchMode Mode { get; set; } + public MemoryDomain Domain { get; set; } + public WatchSize Size { get; set; } + public bool CheckMisAligned { get; set; } + + /*Can be changed mid-search*/ + public DisplayType Type { get; set; } + public bool BigEndian { get; set; } + public PreviousType PreviousType { get; set; } + } + } +}