using System; using System.Collections.Generic; using System.Linq; using BizHawk.Common; using BizHawk.Common.CollectionExtensions; using BizHawk.Common.NumberExtensions; using BizHawk.Emulation.Common; // ReSharper disable PossibleInvalidCastExceptionInForeachLoop namespace BizHawk.Client.Common.RamSearchEngine { public class RamSearchEngine { private Compare _compareTo = Compare.Previous; private List _watchList = new List(); private readonly SearchEngineSettings _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(SearchEngineSettings settings, IMemoryDomains memoryDomains) { _settings = new SearchEngineSettings(memoryDomains) { Mode = settings.Mode, Domain = settings.Domain, Size = settings.Size, CheckMisAligned = settings.CheckMisAligned, Type = settings.Type, BigEndian = settings.BigEndian, PreviousType = settings.PreviousType }; } public RamSearchEngine(SearchEngineSettings 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: for (int i = 0; i < domain.Size; i++) { if (_settings.IsDetailed()) { _watchList.Add(new MiniByteWatchDetailed(domain, i)); } else { _watchList.Add(new MiniByteWatch(domain, i)); } } break; case WatchSize.Word: for (int i = 0; i < domain.Size - 1; i += _settings.CheckMisAligned ? 1 : 2) { if (_settings.IsDetailed()) { _watchList.Add(new MiniWordWatchDetailed(domain, i, _settings.BigEndian)); } else { _watchList.Add(new MiniWordWatch(domain, i, _settings.BigEndian)); } } break; case WatchSize.DWord: for (int i = 0; i < domain.Size - 3; i += _settings.CheckMisAligned ? 1 : 4) { if (_settings.IsDetailed()) { _watchList.Add(new MiniDWordWatchDetailed(domain, i, _settings.BigEndian)); } else { _watchList.Add(new MiniDWordWatch(domain, i, _settings.BigEndian)); } } break; } } /// /// Exposes the current watch state based on index /// public Watch this[int index] => Watch.GenerateWatch( _settings.Domain, _watchList[index].Address, _settings.Size, _settings.Type, _settings.BigEndian, "", 0, _watchList[index].Previous, _settings.IsDetailed() ? ((IMiniWatchDetails)_watchList[index]).ChangeCount : 0); 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 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.IsDetailed()) { 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.IsFastMode() && 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.IsDetailed()) { 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(IEnumerable addresses, bool append) { if (!append) { _watchList.Clear(); } var list = _settings.Size switch { WatchSize.Byte => addresses.ToBytes(_settings), WatchSize.Word => addresses.ToWords(_settings), WatchSize.DWord => addresses.ToDWords(_settings), _ => addresses.ToBytes(_settings) }; _watchList.AddRange(list); } public void Sort(string column, bool reverse) { _isSorted = column == WatchList.Address && !reverse; switch (column) { case WatchList.Address: _watchList = _watchList.OrderBy(w => w.Address, reverse).ToList(); break; case WatchList.Value: _watchList = _watchList.OrderBy(w => GetValue(w.Address), reverse).ToList(); break; case WatchList.Prev: _watchList = _watchList.OrderBy(w => w.Previous, reverse).ToList(); break; case WatchList.ChangesCol: if (_settings.IsDetailed()) { _watchList = _watchList .Cast() .OrderBy(w => w.ChangeCount, reverse) .Cast() .ToList(); } break; case WatchList.Diff: _watchList = _watchList.OrderBy(w => GetValue(w.Address) - w.Previous, reverse).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 => GetValue(w.Address).ToFloat().HawkFloatEquality(w.Previous.ToFloat())) : 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 => GetValue(w.Address).ToFloat() > w.Previous.ToFloat()) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) > SignExtendAsNeeded(w.Previous)); case ComparisonOperator.GreaterThanEqual: return _settings.Type == DisplayType.Float ? watchList.Where(w => GetValue(w.Address).ToFloat() >= w.Previous.ToFloat()) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) >= SignExtendAsNeeded(w.Previous)); case ComparisonOperator.LessThan: return _settings.Type == DisplayType.Float ? watchList.Where(w => GetValue(w.Address).ToFloat() < w.Previous.ToFloat()) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) < SignExtendAsNeeded(w.Previous)); case ComparisonOperator.LessThanEqual: return _settings.Type == DisplayType.Float ? watchList.Where(w => GetValue(w.Address).ToFloat() <= w.Previous.ToFloat()) : 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 => (GetValue(w.Address).ToFloat() + differentBy).HawkFloatEquality(w.Previous.ToFloat()) || (GetValue(w.Address).ToFloat() - differentBy).HawkFloatEquality(w.Previous.ToFloat())); } 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 => GetValue(w.Address).ToFloat().HawkFloatEquality(compareValue.ToFloat())) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) == SignExtendAsNeeded(compareValue)); case ComparisonOperator.NotEqual: return _settings.Type == DisplayType.Float ? watchList.Where(w => !GetValue(w.Address).ToFloat().HawkFloatEquality(compareValue.ToFloat())) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) != SignExtendAsNeeded(compareValue)); case ComparisonOperator.GreaterThan: return _settings.Type == DisplayType.Float ? watchList.Where(w => GetValue(w.Address).ToFloat() > compareValue.ToFloat()) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) > SignExtendAsNeeded(compareValue)); case ComparisonOperator.GreaterThanEqual: return _settings.Type == DisplayType.Float ? watchList.Where(w => GetValue(w.Address).ToFloat() >= compareValue.ToFloat()) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) >= SignExtendAsNeeded(compareValue)); case ComparisonOperator.LessThan: return _settings.Type == DisplayType.Float ? watchList.Where(w => GetValue(w.Address).ToFloat() < compareValue.ToFloat()) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) < SignExtendAsNeeded(compareValue)); case ComparisonOperator.LessThanEqual: return _settings.Type == DisplayType.Float ? watchList.Where(w => GetValue(w.Address).ToFloat() <= compareValue.ToFloat()) : 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 => (GetValue(w.Address).ToFloat() + differentBy).HawkFloatEquality(compareValue) || (GetValue(w.Address).ToFloat() - differentBy).HawkFloatEquality(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.IsDetailed() && CompareValue.HasValue) { var compareValue = CompareValue.Value; switch (Operator) { default: case ComparisonOperator.Equal: return watchList .Cast() .Where(w => w.ChangeCount == compareValue); case ComparisonOperator.NotEqual: return watchList .Cast() .Where(w => w.ChangeCount != compareValue); case ComparisonOperator.GreaterThan: return watchList .Cast() .Where(w => w.ChangeCount > compareValue); case ComparisonOperator.GreaterThanEqual: return watchList .Cast() .Where(w => w.ChangeCount >= compareValue); case ComparisonOperator.LessThan: return watchList .Cast() .Where(w => w.ChangeCount < compareValue); case ComparisonOperator.LessThanEqual: return watchList .Cast() .Where(w => w.ChangeCount <= compareValue); case ComparisonOperator.DifferentBy: if (DifferentBy.HasValue) { return watchList .Cast() .Where(w => w.ChangeCount + DifferentBy.Value == compareValue || w.ChangeCount - DifferentBy.Value == compareValue); } 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 => (GetValue(w.Address).ToFloat() - w.Previous.ToFloat()).HawkFloatEquality(compareValue)) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) == compareValue); case ComparisonOperator.NotEqual: return _settings.Type == DisplayType.Float ? watchList.Where(w => !(GetValue(w.Address).ToFloat() - w.Previous.ToFloat()).HawkFloatEquality(compareValue)) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) != compareValue); case ComparisonOperator.GreaterThan: return _settings.Type == DisplayType.Float ? watchList.Where(w => GetValue(w.Address).ToFloat() - w.Previous.ToFloat() > compareValue) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) > compareValue); case ComparisonOperator.GreaterThanEqual: return _settings.Type == DisplayType.Float ? watchList.Where(w => GetValue(w.Address).ToFloat() - w.Previous.ToFloat() >= compareValue) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) >= compareValue); case ComparisonOperator.LessThan: return _settings.Type == DisplayType.Float ? watchList.Where(w => GetValue(w.Address).ToFloat() - w.Previous.ToFloat() < compareValue) : watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) < compareValue); case ComparisonOperator.LessThanEqual: return _settings.Type == DisplayType.Float ? watchList.Where(w => GetValue(w.Address).ToFloat() - w.Previous.ToFloat() <= 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 => (GetValue(w.Address).ToFloat() - w.Previous.ToFloat() + differentBy).HawkFloatEquality(compareValue) || (GetValue(w.Address).ToFloat() - w.Previous.ToFloat() - differentBy).HawkFloatEquality(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 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 { SearchMode.Detailed => true, SearchMode.Fast => (compareType != Compare.Changes), _ => true }; } } }