diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/Extensions.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/Extensions.cs index d4b1f38159..e186fddeea 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/Extensions.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/Extensions.cs @@ -6,22 +6,43 @@ namespace BizHawk.Client.Common.RamSearchEngine { internal static class Extensions { + public static float ToFloat(this long val) + { + var bytes = BitConverter.GetBytes((int)val); + return BitConverter.ToSingle(bytes, 0); + } + public static IEnumerable ToBytes(this IEnumerable addresses, SearchEngineSettings settings) - => addresses.ToDetailedBytes(settings.Domain); + => settings.IsDetailed() + ? addresses.ToDetailedBytes(settings.Domain) + : addresses.ToBytes(settings.Domain); public static IEnumerable ToWords(this IEnumerable addresses, SearchEngineSettings settings) - => addresses.ToDetailedWords(settings.Domain, settings.BigEndian); + => settings.IsDetailed() + ? addresses.ToDetailedWords(settings.Domain, settings.BigEndian) + : addresses.ToWords(settings.Domain, settings.BigEndian); public static IEnumerable ToDWords(this IEnumerable addresses, SearchEngineSettings settings) - => addresses.ToDetailedDWords(settings.Domain, settings.BigEndian); + => settings.IsDetailed() + ? addresses.ToDetailedDWords(settings.Domain, settings.BigEndian) + : addresses.ToDWords(settings.Domain, settings.BigEndian); - private static IEnumerable ToDetailedBytes(this IEnumerable addresses, MemoryDomain domain) + private static IEnumerable ToBytes(this IEnumerable addresses, MemoryDomain domain) => addresses.Select(a => new MiniByteWatch(domain, a)); - private static IEnumerable ToDetailedWords(this IEnumerable addresses, MemoryDomain domain, bool bigEndian) + private static IEnumerable ToDetailedBytes(this IEnumerable addresses, MemoryDomain domain) + => addresses.Select(a => new MiniByteWatchDetailed(domain, a)); + + private static IEnumerable ToWords(this IEnumerable addresses, MemoryDomain domain, bool bigEndian) => addresses.Select(a => new MiniWordWatch(domain, a, bigEndian)); - private static IEnumerable ToDetailedDWords(this IEnumerable addresses, MemoryDomain domain, bool bigEndian) + private static IEnumerable ToDetailedWords(this IEnumerable addresses, MemoryDomain domain, bool bigEndian) + => addresses.Select(a => new MiniWordWatchDetailed(domain, a, bigEndian)); + + private static IEnumerable ToDWords(this IEnumerable addresses, MemoryDomain domain, bool bigEndian) => addresses.Select(a => new MiniDWordWatch(domain, a, bigEndian)); + + private static IEnumerable ToDetailedDWords(this IEnumerable addresses, MemoryDomain domain, bool bigEndian) + => addresses.Select(a => new MiniDWordWatchDetailed(domain, a, bigEndian)); } } diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs index 33a37a2c93..1bd7b86966 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs @@ -4,172 +4,130 @@ namespace BizHawk.Client.Common.RamSearchEngine { /// /// 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. - long Current { get; } - int ChangeCount { get; } - void ClearChangeCount(); - void SetPreviousToCurrent(); + void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian); bool IsValid(MemoryDomain domain); - void Update(PreviousType type, MemoryDomain domain, bool bigEndian); } internal sealed class MiniByteWatch : IMiniWatch { public long Address { get; } - private byte _previous; - private byte _current; public MiniByteWatch(MemoryDomain domain, long addr) { Address = addr; - _previous = _current = GetByte(domain); - } - - public void SetPreviousToCurrent() - { - _previous = _current; + _previous = GetByte(Address, domain); } public long Previous => _previous; - public long Current => _current; - public int ChangeCount { get; private set; } - - public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) + public bool IsValid(MemoryDomain domain) { - var newValue = GetByte(domain); - - if (newValue != _current) - { - ChangeCount++; - if (type == PreviousType.LastChange) - { - _previous = _current; - } - } - - if (type == PreviousType.LastFrame) - _previous = _current; - - _current = newValue; + return IsValid(Address, domain); } - public void ClearChangeCount() => ChangeCount = 0; - - public bool IsValid(MemoryDomain domain) => Address < domain.Size; - - public byte GetByte(MemoryDomain domain) + public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) { - return IsValid(domain) ? domain.PeekByte(Address) : (byte)0; + _previous = GetByte(Address, domain); + } + + public static bool IsValid(long address, MemoryDomain domain) + { + return address < domain.Size; + } + + public static byte GetByte(long address, MemoryDomain domain) + { + if (!IsValid(address, domain)) + { + return 0; + } + + return domain.PeekByte(address); } } internal sealed class MiniWordWatch : IMiniWatch { public long Address { get; } - private ushort _previous; - private ushort _current; public MiniWordWatch(MemoryDomain domain, long addr, bool bigEndian) { Address = addr; - _previous = _current = GetUshort(domain, bigEndian); - } - - public void SetPreviousToCurrent() - { - _previous = _current; + _previous = GetUshort(Address, domain, bigEndian); } public long Previous => _previous; - public long Current => _current; - public int ChangeCount { get; private set; } - - public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) + public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) { - var newValue = GetUshort(domain, bigEndian); - - if (newValue != _current) - { - ChangeCount++; - if (type == PreviousType.LastChange) - { - _previous = _current; - } - } - - if (type == PreviousType.LastFrame) - _previous = _current; - - _current = newValue; + _previous = GetUshort(Address, domain, bigEndian); } - public void ClearChangeCount() => ChangeCount = 0; - - public bool IsValid(MemoryDomain domain) => Address < domain.Size - 1; - - private ushort GetUshort(MemoryDomain domain, bool bigEndian) + public bool IsValid(MemoryDomain domain) { - return IsValid(domain) ? domain.PeekUshort(Address, bigEndian) : (ushort)0; + return IsValid(Address, domain); + } + + public static bool IsValid(long address, MemoryDomain domain) + { + return address < (domain.Size - 1); + } + + public static ushort GetUshort(long address, MemoryDomain domain, bool bigEndian) + { + if (!IsValid(address, domain)) + { + return 0; + } + + return domain.PeekUshort(address, bigEndian); } } internal sealed class MiniDWordWatch : IMiniWatch { public long Address { get; } - private uint _previous; - private uint _current; public MiniDWordWatch(MemoryDomain domain, long addr, bool bigEndian) { Address = addr; - _previous = _current = GetUint(domain, bigEndian); - } - - public void SetPreviousToCurrent() - { - _previous = _current; + _previous = GetUint(Address, domain, bigEndian); } public long Previous => _previous; - public long Current => _current; - public int ChangeCount { get; private set; } - - public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) + public void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) { - var newValue = GetUint(domain, bigEndian); - - if (newValue != _current) - { - ChangeCount++; - if (type == PreviousType.LastChange) - { - _previous = _current; - } - } - - if (type == PreviousType.LastFrame) - _previous = _current; - - _current = newValue; + _previous = GetUint(Address, domain, bigEndian); } - public void ClearChangeCount() => ChangeCount = 0; - - public bool IsValid(MemoryDomain domain) => Address < domain.Size - 3; - - private uint GetUint(MemoryDomain domain, bool bigEndian) + public bool IsValid(MemoryDomain domain) { - return IsValid(domain) ? domain.PeekUint(Address, bigEndian) : 0; + return IsValid(Address, domain); + } + + public static bool IsValid(long address, MemoryDomain domain) + { + return address < (domain.Size - 3); + } + + public static uint GetUint(long address, MemoryDomain domain, bool bigEndian) + { + if (!IsValid(address, domain)) + { + return 0; + } + + return domain.PeekUint(address, bigEndian); } } } diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs new file mode 100644 index 0000000000..024eae8d78 --- /dev/null +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs @@ -0,0 +1,183 @@ +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.Common.RamSearchEngine +{ + /// + /// 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 = MiniByteWatch.GetByte(Address, domain); + } + + public long Previous => _previous; + + public int ChangeCount { get; private set; } + + public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) + { + var value = MiniByteWatch.GetByte(Address, domain); + + 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; + + public bool IsValid(MemoryDomain domain) => MiniByteWatch.IsValid(Address, domain); + } + + internal sealed class MiniWordWatchDetailed : 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 = MiniWordWatch.GetUshort(Address, domain, bigEndian); + } + + public long Previous => _previous; + + public int ChangeCount { get; private set; } + + public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) + { + var value = MiniWordWatch.GetUshort(Address, domain, 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 bool IsValid(MemoryDomain domain) => MiniWordWatch.IsValid(Address, domain); + } + + internal sealed class MiniDWordWatchDetailed : 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 = MiniDWordWatch.GetUint(Address, domain, bigEndian); + } + + public long Previous => (int)_previous; + + public int ChangeCount { get; private set; } + + public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) + { + var value = MiniDWordWatch.GetUint(Address, domain, 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 bool IsValid(MemoryDomain domain) => MiniDWordWatch.IsValid(Address, domain); + } +} diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs index 4e922c8a9d..7ad70d5704 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs @@ -20,9 +20,10 @@ namespace BizHawk.Client.Common.RamSearchEngine private Compare _compareTo = Compare.Previous; - private IMiniWatch[] _watchList = [ ]; + private IMiniWatch[] _watchList = Array.Empty(); private readonly SearchEngineSettings _settings; - private readonly UndoHistory> _history = new(true, [ ]); //TODO use IList instead of IEnumerable and stop calling `.ToArray()` (i.e. cloning) on reads and writes? + private readonly UndoHistory> _history = new UndoHistory>(true, new List()); //TODO use IList instead of IEnumerable and stop calling `.ToArray()` (i.e. cloning) on reads and writes? + private bool _isSorted = true; // Tracks whether or not the array is sorted by address, if it is, binary search can be used for finding watches public RamSearchEngine(SearchEngineSettings settings, IMemoryDomains memoryDomains) { @@ -63,13 +64,46 @@ namespace BizHawk.Client.Common.RamSearchEngine { default: case WatchSize.Byte: - for (var i = 0; i < _watchList.Length; i++) _watchList[i] = new MiniByteWatch(domain, i); + if (_settings.IsDetailed()) + { + for (var i = 0; i < _watchList.Length; i++) _watchList[i] = new MiniByteWatchDetailed(domain, i); + } + else + { + for (var i = 0; i < _watchList.Length; i++) _watchList[i] = new MiniByteWatch(domain, i); + } break; case WatchSize.Word: - for (var i = 0; i < _watchList.Length; i++) _watchList[i] = new MiniWordWatch(domain, i * stepSize, _settings.BigEndian); + if (_settings.IsDetailed()) + { + for (var i = 0; i < _watchList.Length; i++) + { + _watchList[i] = new MiniWordWatchDetailed(domain, i * stepSize, _settings.BigEndian); + } + } + else + { + for (var i = 0; i < _watchList.Length; i++) + { + _watchList[i] = new MiniWordWatch(domain, i * stepSize, _settings.BigEndian); + } + } break; case WatchSize.DWord: - for (var i = 0; i < _watchList.Length; i++) _watchList[i] = new MiniDWordWatch(domain, i * stepSize, _settings.BigEndian); + if (_settings.IsDetailed()) + { + for (var i = 0; i < _watchList.Length; i++) + { + _watchList[i] = new MiniDWordWatchDetailed(domain, i * stepSize, _settings.BigEndian); + } + } + else + { + for (var i = 0; i < _watchList.Length; i++) + { + _watchList[i] = new MiniDWordWatch(domain, i * stepSize, _settings.BigEndian); + } + } break; } } @@ -87,14 +121,12 @@ namespace BizHawk.Client.Common.RamSearchEngine "", 0, _watchList[index].Previous, - _settings.IsDetailed() ? _watchList[index].ChangeCount : 0); + _settings.IsDetailed() ? ((IMiniWatchDetails)_watchList[index]).ChangeCount : 0); - public int DoSearch(bool updatePrevious) + public int DoSearch() { int before = _watchList.Length; - Update(updatePrevious); - using (Domain.EnterExit()) { _watchList = _compareTo switch @@ -106,6 +138,11 @@ namespace BizHawk.Client.Common.RamSearchEngine Compare.Difference => CompareDifference(_watchList).ToArray(), _ => ComparePrevious(_watchList).ToArray() }; + + if (_settings.PreviousType == PreviousType.LastSearch) + { + SetPreviousToCurrent(); + } } if (UndoEnabled) @@ -116,11 +153,11 @@ namespace BizHawk.Client.Common.RamSearchEngine return before - _watchList.Length; } - public bool Preview(int index) + public bool Preview(long address) { - var addressWatch = _watchList[index]; - addressWatch.Update(PreviousType.Original, _settings.Domain, _settings.BigEndian); - IMiniWatch[] listOfOne = [ addressWatch ]; + var listOfOne = Enumerable.Repeat(_isSorted + ? _watchList.BinarySearch(w => w.Address, address) + : _watchList.FirstOrDefault(w => w.Address == address), 1); return _compareTo switch { @@ -166,12 +203,13 @@ namespace BizHawk.Client.Common.RamSearchEngine /// public int? DifferentBy { get; set; } - public void Update(bool updatePrevious) + public void Update() { + if (!_settings.IsDetailed()) return; using var @lock = _settings.Domain.EnterExit(); - foreach (var watch in _watchList) + foreach (IMiniWatchDetails watch in _watchList) { - watch.Update(updatePrevious ? _settings.PreviousType : PreviousType.Original, _settings.Domain, _settings.BigEndian); + watch.Update(_settings.PreviousType, _settings.Domain, _settings.BigEndian); } } @@ -179,18 +217,26 @@ namespace BizHawk.Client.Common.RamSearchEngine public void SetEndian(bool bigEndian) => _settings.BigEndian = bigEndian; - public void SetPreviousType(PreviousType type) => _settings.PreviousType = type; + /// is and is + public void SetPreviousType(PreviousType type) + { + if (_settings.IsFastMode() && type == PreviousType.LastFrame) + { + throw new InvalidOperationException(); + } - public void SetMode(SearchMode mode) => _settings.Mode = mode; + _settings.PreviousType = type; + } public void SetPreviousToCurrent() { - Array.ForEach(_watchList, static w => w.SetPreviousToCurrent()); + Array.ForEach(_watchList, w => w.SetPreviousToCurrent(_settings.Domain, _settings.BigEndian)); } public void ClearChangeCounts() { - foreach (var watch in _watchList) + if (!_settings.IsDetailed()) return; + foreach (var watch in _watchList.Cast()) { watch.ClearChangeCount(); } @@ -238,26 +284,33 @@ namespace BizHawk.Client.Common.RamSearchEngine }; _watchList = (append ? _watchList.Concat(list) : list).ToArray(); + _isSorted = false; //TODO can this be smarter, such as by inserting instead of appending? } public void Sort(string column, bool reverse) { + _isSorted = column == WatchList.Address && !reverse; switch (column) { case WatchList.Address: _watchList = _watchList.OrderBy(w => w.Address, reverse).ToArray(); break; case WatchList.Value: - _watchList = _watchList.OrderBy(w => w.Current, reverse).ToArray(); + _watchList = _watchList.OrderBy(w => GetValue(w.Address), reverse).ToArray(); break; case WatchList.Prev: _watchList = _watchList.OrderBy(w => w.Previous, reverse).ToArray(); break; case WatchList.ChangesCol: - _watchList = _watchList.OrderBy(w => w.ChangeCount, reverse).ToArray(); + if (!_settings.IsDetailed()) break; + _watchList = _watchList + .Cast() + .OrderBy(w => w.ChangeCount, reverse) + .Cast() + .ToArray(); break; case WatchList.Diff: - _watchList = _watchList.OrderBy(w => w.Current - w.Previous, reverse).ToArray(); + _watchList = _watchList.OrderBy(w => GetValue(w.Address) - w.Previous, reverse).ToArray(); break; } } @@ -306,52 +359,52 @@ namespace BizHawk.Client.Common.RamSearchEngine { default: case ComparisonOperator.Equal: - return watchList.Where(w => SignExtendAsNeeded(w.Current) == SignExtendAsNeeded(w.Previous)); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) == SignExtendAsNeeded(w.Previous)); case ComparisonOperator.NotEqual: - return watchList.Where(w => SignExtendAsNeeded(w.Current) != SignExtendAsNeeded(w.Previous)); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) != SignExtendAsNeeded(w.Previous)); case ComparisonOperator.GreaterThan: - return watchList.Where(w => SignExtendAsNeeded(w.Current) > SignExtendAsNeeded(w.Previous)); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) > SignExtendAsNeeded(w.Previous)); case ComparisonOperator.GreaterThanEqual: - return watchList.Where(w => SignExtendAsNeeded(w.Current) >= SignExtendAsNeeded(w.Previous)); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) >= SignExtendAsNeeded(w.Previous)); case ComparisonOperator.LessThan: - return watchList.Where(w => SignExtendAsNeeded(w.Current) < SignExtendAsNeeded(w.Previous)); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) < SignExtendAsNeeded(w.Previous)); case ComparisonOperator.LessThanEqual: - return watchList.Where(w => SignExtendAsNeeded(w.Current) <= SignExtendAsNeeded(w.Previous)); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) <= SignExtendAsNeeded(w.Previous)); case ComparisonOperator.DifferentBy: if (DifferentBy is not int differentBy) throw new InvalidOperationException(); return watchList.Where(w => - differentBy == Math.Abs(SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous))); + differentBy == Math.Abs(SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous))); } } switch (Operator) { default: case ComparisonOperator.Equal: - return watchList.Where(w => ReinterpretAsF32(w.Current).HawkFloatEquality(ReinterpretAsF32(w.Previous))); + return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)).HawkFloatEquality(ReinterpretAsF32(w.Previous))); case ComparisonOperator.NotEqual: - return watchList.Where(w => !ReinterpretAsF32(w.Current).HawkFloatEquality(ReinterpretAsF32(w.Previous))); + return watchList.Where(w => !ReinterpretAsF32(GetValue(w.Address)).HawkFloatEquality(ReinterpretAsF32(w.Previous))); case ComparisonOperator.GreaterThan: - return watchList.Where(w => ReinterpretAsF32(w.Current) > ReinterpretAsF32(w.Previous)); + return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)) > ReinterpretAsF32(w.Previous)); case ComparisonOperator.GreaterThanEqual: return watchList.Where(w => { - var val = ReinterpretAsF32(w.Current); + var val = ReinterpretAsF32(GetValue(w.Address)); var prev = ReinterpretAsF32(w.Previous); return val > prev || val.HawkFloatEquality(prev); }); case ComparisonOperator.LessThan: - return watchList.Where(w => ReinterpretAsF32(w.Current) < ReinterpretAsF32(w.Previous)); + return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)) < ReinterpretAsF32(w.Previous)); case ComparisonOperator.LessThanEqual: return watchList.Where(w => { - var val = ReinterpretAsF32(w.Current); + var val = ReinterpretAsF32(GetValue(w.Address)); var prev = ReinterpretAsF32(w.Previous); return val < prev || val.HawkFloatEquality(prev); }); case ComparisonOperator.DifferentBy: if (DifferentBy is not int differentBy) throw new InvalidOperationException(); var differentByF = ReinterpretAsF32(differentBy); - return watchList.Where(w => Math.Abs(ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous)) + return watchList.Where(w => Math.Abs(ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous)) .HawkFloatEquality(differentByF)); } } @@ -365,21 +418,21 @@ namespace BizHawk.Client.Common.RamSearchEngine { default: case ComparisonOperator.Equal: - return watchList.Where(w => SignExtendAsNeeded(w.Current) == SignExtendAsNeeded(compareValue)); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) == SignExtendAsNeeded(compareValue)); case ComparisonOperator.NotEqual: - return watchList.Where(w => SignExtendAsNeeded(w.Current) != SignExtendAsNeeded(compareValue)); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) != SignExtendAsNeeded(compareValue)); case ComparisonOperator.GreaterThan: - return watchList.Where(w => SignExtendAsNeeded(w.Current) > SignExtendAsNeeded(compareValue)); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) > SignExtendAsNeeded(compareValue)); case ComparisonOperator.GreaterThanEqual: - return watchList.Where(w => SignExtendAsNeeded(w.Current) >= SignExtendAsNeeded(compareValue)); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) >= SignExtendAsNeeded(compareValue)); case ComparisonOperator.LessThan: - return watchList.Where(w => SignExtendAsNeeded(w.Current) < SignExtendAsNeeded(compareValue)); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) < SignExtendAsNeeded(compareValue)); case ComparisonOperator.LessThanEqual: - return watchList.Where(w => SignExtendAsNeeded(w.Current) <= SignExtendAsNeeded(compareValue)); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) <= SignExtendAsNeeded(compareValue)); case ComparisonOperator.DifferentBy: if (DifferentBy is not int differentBy) throw new InvalidOperationException(); return watchList.Where(w => - differentBy == Math.Abs(SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(compareValue))); + differentBy == Math.Abs(SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(compareValue))); } } var compareValueF = ReinterpretAsF32(compareValue); @@ -387,29 +440,29 @@ namespace BizHawk.Client.Common.RamSearchEngine { default: case ComparisonOperator.Equal: - return watchList.Where(w => ReinterpretAsF32(w.Current).HawkFloatEquality(compareValueF)); + return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)).HawkFloatEquality(compareValueF)); case ComparisonOperator.NotEqual: - return watchList.Where(w => !ReinterpretAsF32(w.Current).HawkFloatEquality(compareValueF)); + return watchList.Where(w => !ReinterpretAsF32(GetValue(w.Address)).HawkFloatEquality(compareValueF)); case ComparisonOperator.GreaterThan: - return watchList.Where(w => ReinterpretAsF32(w.Current) > compareValueF); + return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)) > compareValueF); case ComparisonOperator.GreaterThanEqual: return watchList.Where(w => { - var val = ReinterpretAsF32(w.Current); + var val = ReinterpretAsF32(GetValue(w.Address)); return val > compareValueF || val.HawkFloatEquality(compareValueF); }); case ComparisonOperator.LessThan: - return watchList.Where(w => ReinterpretAsF32(w.Current) < compareValueF); + return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)) < compareValueF); case ComparisonOperator.LessThanEqual: return watchList.Where(w => { - var val = ReinterpretAsF32(w.Current); + var val = ReinterpretAsF32(GetValue(w.Address)); return val < compareValueF || val.HawkFloatEquality(compareValueF); }); case ComparisonOperator.DifferentBy: if (DifferentBy is not int differentBy) throw new InvalidOperationException(); var differentByF = ReinterpretAsF32(differentBy); - return watchList.Where(w => Math.Abs(ReinterpretAsF32(w.Current) - compareValueF) + return watchList.Where(w => Math.Abs(ReinterpretAsF32(GetValue(w.Address)) - compareValueF) .HawkFloatEquality(differentByF)); } } @@ -440,25 +493,40 @@ namespace BizHawk.Client.Common.RamSearchEngine private IEnumerable CompareChanges(IEnumerable watchList) { - if (CompareValue is not long compareValue) throw new InvalidOperationException(); + if (!_settings.IsDetailed()) throw new InvalidCastException(); //TODO matches previous behaviour; was this intended to skip processing? --yoshi + if (CompareValue is not long compareValue) throw new InvalidCastException(); //TODO typo for IOE? switch (Operator) { default: case ComparisonOperator.Equal: - return watchList.Where(w => w.ChangeCount == compareValue); + return watchList + .Cast() + .Where(w => w.ChangeCount == compareValue); case ComparisonOperator.NotEqual: - return watchList.Where(w => w.ChangeCount != compareValue); + return watchList + .Cast() + .Where(w => w.ChangeCount != compareValue); case ComparisonOperator.GreaterThan: - return watchList.Where(w => w.ChangeCount > compareValue); + return watchList + .Cast() + .Where(w => w.ChangeCount > compareValue); case ComparisonOperator.GreaterThanEqual: - return watchList.Where(w => w.ChangeCount >= compareValue); + return watchList + .Cast() + .Where(w => w.ChangeCount >= compareValue); case ComparisonOperator.LessThan: - return watchList.Where(w => w.ChangeCount < compareValue); + return watchList + .Cast() + .Where(w => w.ChangeCount < compareValue); case ComparisonOperator.LessThanEqual: - return watchList.Where(w => w.ChangeCount <= compareValue); + return watchList + .Cast() + .Where(w => w.ChangeCount <= compareValue); case ComparisonOperator.DifferentBy: if (DifferentBy is not int differentBy) throw new InvalidOperationException(); - return watchList.Where(w => Math.Abs(w.ChangeCount - compareValue) == differentBy); + return watchList + .Cast() + .Where(w => Math.Abs(w.ChangeCount - compareValue) == differentBy); } } @@ -471,21 +539,21 @@ namespace BizHawk.Client.Common.RamSearchEngine { default: case ComparisonOperator.Equal: - return watchList.Where(w => SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) == compareValue); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) == compareValue); case ComparisonOperator.NotEqual: - return watchList.Where(w => SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) != compareValue); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) != compareValue); case ComparisonOperator.GreaterThan: - return watchList.Where(w => SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) > compareValue); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) > compareValue); case ComparisonOperator.GreaterThanEqual: - return watchList.Where(w => SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) >= compareValue); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) >= compareValue); case ComparisonOperator.LessThan: - return watchList.Where(w => SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) < compareValue); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) < compareValue); case ComparisonOperator.LessThanEqual: - return watchList.Where(w => SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) <= compareValue); + return watchList.Where(w => SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) <= compareValue); case ComparisonOperator.DifferentBy: if (DifferentBy is not int differentBy) throw new InvalidOperationException(); return watchList.Where(w => - differentBy == Math.Abs(SignExtendAsNeeded(w.Current) - SignExtendAsNeeded(w.Previous) - compareValue)); + differentBy == Math.Abs(SignExtendAsNeeded(GetValue(w.Address)) - SignExtendAsNeeded(w.Previous) - compareValue)); } } var compareValueF = ReinterpretAsF32(compareValue); @@ -493,29 +561,29 @@ namespace BizHawk.Client.Common.RamSearchEngine { default: case ComparisonOperator.Equal: - return watchList.Where(w => (ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous)).HawkFloatEquality(compareValueF)); + return watchList.Where(w => (ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous)).HawkFloatEquality(compareValueF)); case ComparisonOperator.NotEqual: - return watchList.Where(w => !(ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous)).HawkFloatEquality(compareValueF)); + return watchList.Where(w => !(ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous)).HawkFloatEquality(compareValueF)); case ComparisonOperator.GreaterThan: - return watchList.Where(w => ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous) > compareValueF); + return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous) > compareValueF); case ComparisonOperator.GreaterThanEqual: return watchList.Where(w => { - var diff = ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous); + var diff = ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous); return diff > compareValueF || diff.HawkFloatEquality(compareValueF); }); case ComparisonOperator.LessThan: - return watchList.Where(w => ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous) < compareValueF); + return watchList.Where(w => ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous) < compareValueF); case ComparisonOperator.LessThanEqual: return watchList.Where(w => { - var diff = ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous); + var diff = ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous); return diff < compareValueF || diff.HawkFloatEquality(compareValueF); }); case ComparisonOperator.DifferentBy: if (DifferentBy is not int differentBy) throw new InvalidOperationException(); var differentByF = ReinterpretAsF32(differentBy); - return watchList.Where(w => Math.Abs(ReinterpretAsF32(w.Current) - ReinterpretAsF32(w.Previous) - compareValueF) + return watchList.Where(w => Math.Abs(ReinterpretAsF32(GetValue(w.Address)) - ReinterpretAsF32(w.Previous) - compareValueF) .HawkFloatEquality(differentByF)); } } @@ -536,12 +604,24 @@ namespace BizHawk.Client.Common.RamSearchEngine }; } + private long GetValue(long addr) + { + // do not return sign extended variables from here. + return _settings.Size switch + { + WatchSize.Byte => MiniByteWatch.GetByte(addr, Domain), + WatchSize.Word => MiniWordWatch.GetUshort(addr, Domain, _settings.BigEndian), + WatchSize.DWord => MiniDWordWatch.GetUint(addr, Domain, _settings.BigEndian), + _ => MiniByteWatch.GetByte(addr, Domain) + }; + } + private bool CanDoCompareType(Compare compareType) { return _settings.Mode switch { SearchMode.Detailed => true, - SearchMode.Fast => compareType != Compare.Changes, + SearchMode.Fast => (compareType != Compare.Changes), _ => true }; } diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/SearchEngineSettings.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/SearchEngineSettings.cs index 9944a90129..5323c745f7 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/SearchEngineSettings.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/SearchEngineSettings.cs @@ -8,25 +8,28 @@ namespace BizHawk.Client.Common.RamSearchEngine { BigEndian = memoryDomains.MainMemory.EndianType == MemoryDomain.Endian.Big; Size = (WatchSize)memoryDomains.MainMemory.WordSize; + Type = WatchDisplayType.Unsigned; Mode = memoryDomains.MainMemory.Size > 1024 * 1024 ? SearchMode.Fast : SearchMode.Detailed; Domain = memoryDomains.MainMemory; + CheckMisAligned = false; + PreviousType = PreviousType.LastSearch; UseUndoHistory = useUndoHistory; } /*Require restart*/ + 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 WatchDisplayType Type { get; set; } = WatchDisplayType.Unsigned; + public WatchDisplayType Type { get; set; } public bool BigEndian { get; set; } - public PreviousType PreviousType { get; set; } = PreviousType.LastFrame; + public PreviousType PreviousType { get; set; } public bool UseUndoHistory { get; set; } - public SearchMode Mode { get; set; } } public static class SearchEngineSettingsExtensions diff --git a/src/BizHawk.Client.Common/tools/Watch/PreviousType.cs b/src/BizHawk.Client.Common/tools/Watch/PreviousType.cs index 6f08ebd199..4d8442ff2e 100644 --- a/src/BizHawk.Client.Common/tools/Watch/PreviousType.cs +++ b/src/BizHawk.Client.Common/tools/Watch/PreviousType.cs @@ -3,6 +3,7 @@ public enum PreviousType { Original = 0, + LastSearch = 1, LastFrame = 2, LastChange = 3, } diff --git a/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.Designer.cs b/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.Designer.cs index c3b22d4f26..3d73887741 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.Designer.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.Designer.cs @@ -72,6 +72,7 @@ namespace BizHawk.Client.EmuHawk this.DisplayTypeSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.toolStripSeparator1 = new BizHawk.WinForms.Controls.ToolStripSeparatorEx(); this.DefinePreviousValueSubMenu = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); + this.Previous_LastSearchMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.PreviousFrameMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.Previous_OriginalMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); this.Previous_LastChangeMenuItem = new BizHawk.WinForms.Controls.ToolStripMenuItemEx(); @@ -400,12 +401,18 @@ namespace BizHawk.Client.EmuHawk // DefinePreviousValueSubMenu // this.DefinePreviousValueSubMenu.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.Previous_LastSearchMenuItem, this.PreviousFrameMenuItem, this.Previous_OriginalMenuItem, this.Previous_LastChangeMenuItem}); this.DefinePreviousValueSubMenu.Text = "Define Previous Value"; this.DefinePreviousValueSubMenu.DropDownOpened += new System.EventHandler(this.DefinePreviousValueSubMenu_DropDownOpened); // + // Previous_LastSearchMenuItem + // + this.Previous_LastSearchMenuItem.Text = "Last &Search"; + this.Previous_LastSearchMenuItem.Click += new System.EventHandler(this.Previous_LastSearchMenuItem_Click); + // // PreviousFrameMenuItem // this.PreviousFrameMenuItem.Text = "&Previous Frame"; @@ -1130,6 +1137,7 @@ namespace BizHawk.Client.EmuHawk private BizHawk.WinForms.Controls.ToolStripSeparatorEx toolStripSeparator8; private BizHawk.WinForms.Controls.ToolStripMenuItemEx DefinePreviousValueSubMenu; private BizHawk.WinForms.Controls.ToolStripMenuItemEx PreviousFrameMenuItem; + private BizHawk.WinForms.Controls.ToolStripMenuItemEx Previous_LastSearchMenuItem; private BizHawk.WinForms.Controls.ToolStripMenuItemEx Previous_OriginalMenuItem; private System.Windows.Forms.GroupBox CompareToBox; private System.Windows.Forms.RadioButton DifferenceRadio; @@ -1183,4 +1191,4 @@ namespace BizHawk.Client.EmuHawk private ToolStripMenuItemEx SelectAllMenuItem; private ToolStripMenuItemEx SearchMenuItem; } -} +} \ No newline at end of file diff --git a/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs b/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs index bd7edbd18d..a466b9fff3 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Watch/RamSearch.cs @@ -188,7 +188,7 @@ namespace BizHawk.Client.EmuHawk var nextColor = Color.White; var search = _searches[index]; var isCheat = MainForm.CheatList.IsActive(_settings.Domain, search.Address); - var isWeeded = Settings.PreviewMode && !_forcePreviewClear && _searches.Preview(index); + var isWeeded = Settings.PreviewMode && !_forcePreviewClear && _searches.Preview(search.Address); if (!search.IsValid) { @@ -270,6 +270,8 @@ namespace BizHawk.Client.EmuHawk { if (_searches.Count > 0) { + _searches.Update(); + if (_autoSearch) { if (InputPollableCore != null && Settings.AutoSearchTakeLagFramesIntoAccount && InputPollableCore.IsLagFrame) @@ -278,24 +280,21 @@ namespace BizHawk.Client.EmuHawk } else { - DoSearch(true); + DoSearch(); } } - else if (_settings.IsDetailed()) - { - _searches.Update(true); - } _forcePreviewClear = false; WatchListView.RowCount = _searches.Count; } } - // TODO: this seems to be missing some logic from FrameUpdate that probably should exist here private void MinimalUpdate() { if (_searches.Count > 0) { + _searches.Update(); + if (_autoSearch) { DoSearch(); @@ -522,14 +521,14 @@ namespace BizHawk.Client.EmuHawk } } - public void DoSearch(bool updatePrevious = false) + public void DoSearch() { _searches.CompareValue = CompareToValue; _searches.DifferentBy = DifferentByValue; _searches.Operator = Operator; _searches.CompareTo = Compare; - var removed = _searches.DoSearch(updatePrevious); + var removed = _searches.DoSearch(); UpdateList(); SetRemovedMessage(removed); ToggleSearchDependentToolBarItems(); @@ -588,6 +587,7 @@ namespace BizHawk.Client.EmuHawk && _settings.IsDetailed()) { _settings.Mode = SearchMode.Fast; + SetReboot(true); MessageLabel.Text = "Large domain, switching to fast mode"; } } @@ -753,7 +753,6 @@ namespace BizHawk.Client.EmuHawk private void SetToDetailedMode() { _settings.Mode = SearchMode.Detailed; - _searches.SetMode(SearchMode.Detailed); NumberOfChangesRadio.Enabled = true; NumberOfChangesBox.Enabled = true; DifferenceRadio.Enabled = true; @@ -764,6 +763,7 @@ namespace BizHawk.Client.EmuHawk ChangesMenuItem.Checked = true; ColumnToggleCallback(); + SetReboot(true); } private ToolStripMenuItem ChangesMenuItem @@ -782,7 +782,11 @@ namespace BizHawk.Client.EmuHawk private void SetToFastMode() { _settings.Mode = SearchMode.Fast; - _searches.SetMode(SearchMode.Fast); + + if (_settings.PreviousType == PreviousType.LastFrame || _settings.PreviousType == PreviousType.LastChange) + { + SetPreviousType(PreviousType.LastSearch); + } NumberOfChangesRadio.Enabled = false; NumberOfChangesBox.Enabled = false; @@ -798,6 +802,7 @@ namespace BizHawk.Client.EmuHawk ChangesMenuItem.Checked = false; ColumnToggleCallback(); + SetReboot(true); } private void RemoveAddresses() @@ -1079,6 +1084,7 @@ namespace BizHawk.Client.EmuHawk private void DefinePreviousValueSubMenu_DropDownOpened(object sender, EventArgs e) { + Previous_LastSearchMenuItem.Checked = false; PreviousFrameMenuItem.Checked = false; Previous_OriginalMenuItem.Checked = false; Previous_LastChangeMenuItem.Checked = false; @@ -1086,6 +1092,9 @@ namespace BizHawk.Client.EmuHawk switch (_settings.PreviousType) { default: + case PreviousType.LastSearch: + Previous_LastSearchMenuItem.Checked = true; + break; case PreviousType.LastFrame: PreviousFrameMenuItem.Checked = true; break; @@ -1096,6 +1105,9 @@ namespace BizHawk.Client.EmuHawk Previous_LastChangeMenuItem.Checked = true; break; } + + PreviousFrameMenuItem.Enabled = _settings.IsDetailed(); + Previous_LastChangeMenuItem.Enabled = _settings.IsDetailed(); } private void DetailedMenuItem_Click(object sender, EventArgs e) @@ -1134,6 +1146,11 @@ namespace BizHawk.Client.EmuHawk SetPreviousType(PreviousType.LastFrame); } + private void Previous_LastSearchMenuItem_Click(object sender, EventArgs e) + { + SetPreviousType(PreviousType.LastSearch); + } + private void Previous_OriginalMenuItem_Click(object sender, EventArgs e) { SetPreviousType(PreviousType.Original); @@ -1152,6 +1169,8 @@ namespace BizHawk.Client.EmuHawk private void SearchSubMenu_DropDownOpened(object sender, EventArgs e) { + ClearChangeCountsMenuItem.Enabled = _settings.IsDetailed(); + RemoveMenuItem.Enabled = AddToRamWatchMenuItem.Enabled = WatchListView.AnyRowsSelected;