diff --git a/src/BizHawk.Client.Common/tools/Cheat.cs b/src/BizHawk.Client.Common/tools/Cheat.cs index bae019699e3..9f267134ba6 100644 --- a/src/BizHawk.Client.Common/tools/Cheat.cs +++ b/src/BizHawk.Client.Common/tools/Cheat.cs @@ -17,10 +17,17 @@ public enum CompareType private readonly Watch _watch; private int? _compare; - private int _val; + + private long _val; + private bool _enabled; - public Cheat(Watch watch, int value, int? compare = null, bool enabled = true, CompareType comparisonType = CompareType.None) + public Cheat( + Watch watch, + long value, + int? compare = null, + bool enabled = true, + CompareType comparisonType = CompareType.None) { _enabled = enabled; _watch = watch; @@ -67,7 +74,8 @@ public Cheat(Cheat cheat) public long? Address => _watch.Address; - public int? Value => IsSeparator ? null : _val; + public long? Value + => IsSeparator ? null : _val; public bool? BigEndian => IsSeparator ? null : _watch.BigEndian; @@ -97,6 +105,7 @@ public MemoryDomain Domain WatchSize.Byte => ((ByteWatch) _watch).FormatValue((byte)_val), WatchSize.Word => ((WordWatch) _watch).FormatValue((ushort)_val), WatchSize.DWord => ((DWordWatch) _watch).FormatValue((uint)_val), + WatchSize.QWord => ((QWordWatch) _watch).FormatValue(unchecked((ulong) _val)), WatchSize.Separator => "", _ => string.Empty, }; @@ -112,6 +121,7 @@ public string CompareStr WatchSize.Byte => ((ByteWatch) _watch).FormatValue((byte)_compare.Value), WatchSize.Word => ((WordWatch) _watch).FormatValue((ushort)_compare.Value), WatchSize.DWord => ((DWordWatch) _watch).FormatValue((uint)_compare.Value), + WatchSize.QWord => ((QWordWatch) _watch).FormatValue(unchecked((ulong) _compare.Value)), WatchSize.Separator => "", _ => string.Empty, }; @@ -178,6 +188,9 @@ public void Pulse() case WatchSize.DWord: _watch.Poke(((DWordWatch)_watch).FormatValue((uint)_val)); break; + case WatchSize.QWord: + _watch.Poke(((QWordWatch) _watch).FormatValue(unchecked((ulong) _val))); + break; } } @@ -229,10 +242,12 @@ public bool Contains(long addr) return addr == _watch.Address || addr == _watch.Address + 1; case WatchSize.DWord: return addr >= _watch.Address && addr <= _watch.Address + 3; + case WatchSize.QWord: + return _watch.Address <= addr && addr <= _watch.Address + (sizeof(ulong) - 1); } } - public void PokeValue(int val) + public void PokeValue(long val) { if (!IsSeparator) { @@ -245,7 +260,7 @@ public void Increment() if (!IsSeparator) { _val++; - if (_val > _watch.MaxValue) + if (unchecked((ulong) _val) > _watch.MaxValue) { _val = 0; } @@ -260,10 +275,7 @@ public void Decrement() if (!IsSeparator) { _val--; - if ((uint)_val > _watch.MaxValue) - { - _val = (int)_watch.MaxValue; - } + if (unchecked((ulong) _val) > _watch.MaxValue) _val = unchecked((long) _watch.MaxValue); Pulse(); Changes(); diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/Extensions.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/Extensions.cs index 14ff0561270..6d720bd37b1 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/Extensions.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/Extensions.cs @@ -21,6 +21,11 @@ public static IEnumerable ToDWords(this IEnumerable addresses, ? addresses.ToDetailedDWords(settings.Domain, settings.BigEndian) : addresses.ToDWords(settings.Domain, settings.BigEndian); + public static IEnumerable ToQWords(this IEnumerable addresses, SearchEngineSettings settings) + => settings.IsDetailed() + ? addresses.ToDetailedQWords(settings.Domain, settings.BigEndian) + : addresses.ToQWords(settings.Domain, settings.BigEndian); + private static IEnumerable ToBytes(this IEnumerable addresses, MemoryDomain domain) => addresses.Select(a => new MiniByteWatch(domain, a)); @@ -38,5 +43,11 @@ private static IEnumerable ToDWords(this IEnumerable addresses private static IEnumerable ToDetailedDWords(this IEnumerable addresses, MemoryDomain domain, bool bigEndian) => addresses.Select(a => new MiniDWordWatchDetailed(domain, a, bigEndian)); + + private static IEnumerable ToQWords(this IEnumerable addresses, MemoryDomain domain, bool bigEndian) + => addresses.Select(a => new MiniQWordWatch(domain, a, bigEndian)); + + private static IEnumerable ToDetailedQWords(this IEnumerable addresses, MemoryDomain domain, bool bigEndian) + => addresses.Select(a => new MiniQWordWatchDetailed(domain, a, bigEndian)); } } diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs index cccc44b7413..7045637359f 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs @@ -9,125 +9,86 @@ namespace BizHawk.Client.Common.RamSearchEngine internal interface IMiniWatch { long Address { get; } - uint Previous { get; } + + ulong Previous { get; } + + ulong GetValue(MemoryDomain domain, bool bigEndian); + void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian); bool IsValid(MemoryDomain domain); } - internal class MiniByteWatch : IMiniWatch + internal abstract class MiniWatchBase : IMiniWatch { public long Address { get; } - private protected byte _previous; - public MiniByteWatch(MemoryDomain domain, long addr) + public ulong Previous { get; protected set; } + + protected MiniWatchBase(long addr, ulong prevValue) { Address = addr; - _previous = GetByte(Address, domain); + Previous = prevValue; } - public uint Previous => _previous; + public ulong GetValue(MemoryDomain domain, bool bigEndian) + => IsValid(domain) ? GetValueInner(Address, domain, bigEndian: bigEndian) : default; + + protected abstract ulong GetValueInner(long address, MemoryDomain domain, bool bigEndian); public bool IsValid(MemoryDomain domain) - { - return IsValid(Address, domain); - } + => IsValid(Address, domain); + + protected abstract bool IsValid(long address, MemoryDomain domain); public virtual void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) - { - _previous = GetByte(Address, domain); - } + => Previous = GetValueInner(Address, domain, bigEndian: bigEndian); + } - public static bool IsValid(long address, MemoryDomain domain) - { - return address < domain.Size; - } + internal class MiniByteWatch : MiniWatchBase + { + public MiniByteWatch(MemoryDomain domain, long addr) + : base(addr: addr, prevValue: domain.PeekByte(addr)) {} - public static byte GetByte(long address, MemoryDomain domain) - { - if (!IsValid(address, domain)) - { - return 0; - } + protected override ulong GetValueInner(long address, MemoryDomain domain, bool bigEndian) + => domain.PeekByte(address); - return domain.PeekByte(address); - } + protected override bool IsValid(long address, MemoryDomain domain) + => 0L <= address && address < domain.Size; } - internal class MiniWordWatch : IMiniWatch + internal class MiniWordWatch : MiniWatchBase { - public long Address { get; } - private protected ushort _previous; - public MiniWordWatch(MemoryDomain domain, long addr, bool bigEndian) - { - Address = addr; - _previous = GetUshort(Address, domain, bigEndian); - } + : base(addr: addr, prevValue: domain.PeekUshort(addr, bigEndian: bigEndian)) {} - public uint Previous => _previous; + protected override ulong GetValueInner(long address, MemoryDomain domain, bool bigEndian) + => domain.PeekUshort(address, bigEndian); - public virtual void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) - { - _previous = GetUshort(Address, domain, bigEndian); - } - - public bool IsValid(MemoryDomain domain) - { - 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); - } + protected override bool IsValid(long address, MemoryDomain domain) + => 0L <= address && address <= domain.Size - sizeof(ushort); } - internal class MiniDWordWatch : IMiniWatch + internal class MiniDWordWatch : MiniWatchBase { - public long Address { get; } - private protected uint _previous; - public MiniDWordWatch(MemoryDomain domain, long addr, bool bigEndian) - { - Address = addr; - _previous = GetUint(Address, domain, bigEndian); - } + : base(addr: addr, prevValue: domain.PeekUint(addr, bigEndian: bigEndian)) {} - public uint Previous => _previous; + protected override ulong GetValueInner(long address, MemoryDomain domain, bool bigEndian) + => domain.PeekUint(address, bigEndian); - public virtual void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) - { - _previous = GetUint(Address, domain, bigEndian); - } - - public bool IsValid(MemoryDomain domain) - { - return IsValid(Address, domain); - } + protected override bool IsValid(long address, MemoryDomain domain) + => 0L <= address && address <= domain.Size - sizeof(uint); + } - public static bool IsValid(long address, MemoryDomain domain) - { - return address < (domain.Size - 3); - } + internal class MiniQWordWatch : MiniWatchBase + { + public MiniQWordWatch(MemoryDomain domain, long addr, bool bigEndian) + : base(addr: addr, prevValue: domain.PeekUint(addr, bigEndian: bigEndian)) {} - public static uint GetUint(long address, MemoryDomain domain, bool bigEndian) - { - if (!IsValid(address, domain)) - { - return 0; - } + protected override ulong GetValueInner(long address, MemoryDomain domain, bool bigEndian) + => domain.PeekUlong(address, bigEndian); - return domain.PeekUint(address, bigEndian); - } + protected override bool IsValid(long address, MemoryDomain domain) + => 0L <= address && address <= domain.Size - sizeof(ulong); } } diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs index 0062dbaf276..6ecfe0a4552 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs @@ -9,7 +9,8 @@ namespace BizHawk.Client.Common.RamSearchEngine /// internal interface IMiniWatchDetails : IMiniWatch { - uint Current { get; } + ulong Current { get; } + int ChangeCount { get; } void ClearChangeCount(); void Update(PreviousType type, MemoryDomain domain, bool bigEndian); @@ -20,34 +21,30 @@ internal sealed class MiniByteWatchDetailed : MiniByteWatch, IMiniWatchDetails private byte _current; public MiniByteWatchDetailed(MemoryDomain domain, long addr) : base(domain, addr) - { - _previous = _current = GetByte(Address, domain); - } + => Previous = _current = unchecked((byte) GetValueInner(Address, domain, bigEndian: default)); public override void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) - { - _previous = _current; - } + => Previous = _current; - public uint Current => _current; + public ulong Current => _current; public int ChangeCount { get; private set; } public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) { - var newValue = GetByte(Address, domain); + var newValue = unchecked((byte) GetValueInner(Address, domain, bigEndian: default)); if (newValue != _current) { ChangeCount++; if (type is PreviousType.LastChange) { - _previous = _current; + Previous = _current; } } if (type is PreviousType.LastFrame) { - _previous = _current; + Previous = _current; } _current = newValue; @@ -61,34 +58,30 @@ internal sealed class MiniWordWatchDetailed : MiniWordWatch, IMiniWatchDetails private ushort _current; public MiniWordWatchDetailed(MemoryDomain domain, long addr, bool bigEndian) : base(domain, addr, bigEndian) - { - _previous = _current = GetUshort(Address, domain, bigEndian); - } + => Previous = _current = unchecked((ushort) GetValueInner(Address, domain, bigEndian: bigEndian)); public override void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) - { - _previous = _current; - } + => Previous = _current; - public uint Current => _current; + public ulong Current => _current; public int ChangeCount { get; private set; } public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) { - var newValue = GetUshort(Address, domain, bigEndian); + var newValue = unchecked((ushort) GetValueInner(Address, domain, bigEndian: bigEndian)); if (newValue != _current) { ChangeCount++; if (type is PreviousType.LastChange) { - _previous = _current; + Previous = _current; } } if (type is PreviousType.LastFrame) { - _previous = _current; + Previous = _current; } _current = newValue; @@ -102,34 +95,67 @@ internal sealed class MiniDWordWatchDetailed : MiniDWordWatch, IMiniWatchDetails private uint _current; public MiniDWordWatchDetailed(MemoryDomain domain, long addr, bool bigEndian) : base(domain, addr, bigEndian) - { - _previous = _current = GetUint(Address, domain, bigEndian); - } + => Previous = _current = unchecked((uint) GetValueInner(Address, domain, bigEndian: bigEndian)); public override void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) + => Previous = _current; + + public ulong Current => _current; + + public int ChangeCount { get; private set; } + + public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) { - _previous = _current; + var newValue = unchecked((uint) GetValueInner(Address, domain, bigEndian: bigEndian)); + if (newValue != _current) + { + ChangeCount++; + if (type is PreviousType.LastChange) + { + Previous = _current; + } + } + + if (type is PreviousType.LastFrame) + { + Previous = _current; + } + + _current = newValue; } - public uint Current => _current; + public void ClearChangeCount() => ChangeCount = 0; + } + + internal sealed class MiniQWordWatchDetailed : MiniQWordWatch, IMiniWatchDetails + { + private ulong _current; + + public MiniQWordWatchDetailed(MemoryDomain domain, long addr, bool bigEndian) : base(domain, addr, bigEndian) + => Previous = _current = GetValueInner(Address, domain, bigEndian: bigEndian); + + public override void SetPreviousToCurrent(MemoryDomain domain, bool bigEndian) + => Previous = _current; + + public ulong Current => _current; public int ChangeCount { get; private set; } public void Update(PreviousType type, MemoryDomain domain, bool bigEndian) { - var newValue = GetUint(Address, domain, bigEndian); + var newValue = GetValueInner(Address, domain, bigEndian: bigEndian); if (newValue != _current) { ChangeCount++; if (type is PreviousType.LastChange) { - _previous = _current; + Previous = _current; } } if (type is PreviousType.LastFrame) { - _previous = _current; + Previous = _current; } _current = newValue; diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs index 19a9f802f8e..398dab92379 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs @@ -12,6 +12,12 @@ namespace BizHawk.Client.Common.RamSearchEngine { public class RamSearchEngine { + private static bool DifferByExactly(ulong difference, long a, long b) + => unchecked((ulong) Math.Abs(a - b) == difference); + + private static bool DifferByExactly(ulong difference, double a, double b) + => Math.Abs(a - b).HawkFloatEquality(ReinterpretAsF64(difference)); + private Compare _compareTo = Compare.Previous; private IMiniWatch[] _watchList = Array.Empty(); @@ -32,8 +38,13 @@ public RamSearchEngine(SearchEngineSettings settings, IMemoryDomains memoryDomai }; } - public RamSearchEngine(SearchEngineSettings settings, IMemoryDomains memoryDomains, Compare compareTo, uint? compareValue, uint? differentBy) - : this(settings, memoryDomains) + public RamSearchEngine( + SearchEngineSettings settings, + IMemoryDomains memoryDomains, + Compare compareTo, + ulong? compareValue, + ulong? differentBy) + : this(settings, memoryDomains) { _compareTo = compareTo; DifferentBy = differentBy; @@ -98,6 +109,22 @@ public void Start() } } break; + case WatchSize.QWord: + if (_settings.IsDetailed()) + { + for (var i = 0; i < _watchList.Length; i++) + { + _watchList[i] = new MiniQWordWatchDetailed(domain, i * stepSize, _settings.BigEndian); + } + } + else + { + for (var i = 0; i < _watchList.Length; i++) + { + _watchList[i] = new MiniQWordWatch(domain, i * stepSize, _settings.BigEndian); + } + } + break; } } @@ -113,7 +140,7 @@ public void Start() _settings.BigEndian, "", 0, - _watchList[index].Previous, + unchecked((long) _watchList[index].Previous), _settings.IsDetailed() ? ((IMiniWatchDetails)_watchList[index]).ChangeCount : 0); public int DoSearch() @@ -185,7 +212,7 @@ public Compare CompareTo } } - public uint? CompareValue { get; set; } + public ulong? CompareValue { get; set; } public ComparisonOperator Operator { get; set; } @@ -193,7 +220,7 @@ public Compare CompareTo /// 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.
/// it already supported floats by way of reinterpret-cast, it just wasn't implemented correctly on this side --yoshi /// - public uint? DifferentBy { get; set; } + public ulong? DifferentBy { get; set; } public void Update() { @@ -273,6 +300,7 @@ public void AddRange(IEnumerable addresses, bool append) WatchSize.Byte => addresses.ToBytes(_settings), WatchSize.Word => addresses.ToWords(_settings), WatchSize.DWord => addresses.ToDWords(_settings), + WatchSize.QWord => addresses.ToQWords(_settings), _ => addresses.ToBytes(_settings), }; @@ -291,6 +319,8 @@ public void ConvertTo(WatchSize size) WatchSize.Word => addresses.Where(static address => address % 2 == 0).ToWords(_settings).ToArray(), WatchSize.DWord when _settings.CheckMisAligned => addresses.ToDWords(_settings).ToArray(), WatchSize.DWord => addresses.Where(static address => address % 4 == 0).ToDWords(_settings).ToArray(), + WatchSize.QWord when _settings.CheckMisAligned => addresses.ToQWords(_settings).ToArray(), + WatchSize.QWord => addresses.Where(static address => address % sizeof(ulong) is 0).ToQWords(_settings).ToArray(), _ => _watchList, }; @@ -301,28 +331,28 @@ private IEnumerable AllAddresses() { foreach (var watch in _watchList) { - if (_settings.CheckMisAligned) + var addr = watch.Address; + yield return addr; + if (_settings.CheckMisAligned) continue; // I guess we're assuming the other parts of the address are also in `_watchList`? --yoshi + switch (_settings.Size) { - yield return watch.Address; - } - else - { - switch (_settings.Size) - { - case WatchSize.Word: - yield return watch.Address; - yield return watch.Address + 1; - break; - case WatchSize.DWord: - yield return watch.Address; - yield return watch.Address + 1; - yield return watch.Address + 2; - yield return watch.Address + 3; - break; - default: - yield return watch.Address; - break; - } + case WatchSize.Word: + yield return addr + 1; + break; + case WatchSize.DWord: + yield return addr + 1; + yield return addr + 2; + yield return addr + 3; + break; + case WatchSize.QWord: + yield return addr + 1; + yield return addr + 2; + yield return addr + 3; + yield return addr + 4; + yield return addr + 5; + yield return addr + 6; + yield return addr + 7; + break; } } } @@ -410,47 +440,50 @@ private IEnumerable ComparePrevious(IEnumerable watchLis case ComparisonOperator.LessThanEqual: return watchList.Where(w => SignExtendAsNeeded(GetValue(w)) <= SignExtendAsNeeded(w.Previous)); case ComparisonOperator.DifferentBy: - if (DifferentBy is not uint differentBy) throw new InvalidOperationException(); - return watchList.Where(w => - differentBy == Math.Abs(SignExtendAsNeeded(GetValue(w)) - SignExtendAsNeeded(w.Previous))); + if (DifferentBy is not ulong differentBy) throw new InvalidOperationException(); + return watchList.Where(w => DifferByExactly( + difference: differentBy, + SignExtendAsNeeded(GetValue(w)), + SignExtendAsNeeded(w.Previous))); } } switch (Operator) { default: case ComparisonOperator.Equal: - return watchList.Where(w => ReinterpretAsF32(GetValue(w)).HawkFloatEquality(ReinterpretAsF32(w.Previous))); + return watchList.Where(w => ReinterpretAsF64(GetValue(w)).HawkFloatEquality(ReinterpretAsF64(w.Previous))); case ComparisonOperator.NotEqual: - return watchList.Where(w => !ReinterpretAsF32(GetValue(w)).HawkFloatEquality(ReinterpretAsF32(w.Previous))); + return watchList.Where(w => !ReinterpretAsF64(GetValue(w)).HawkFloatEquality(ReinterpretAsF64(w.Previous))); case ComparisonOperator.GreaterThan: - return watchList.Where(w => ReinterpretAsF32(GetValue(w)) > ReinterpretAsF32(w.Previous)); + return watchList.Where(w => ReinterpretAsF64(GetValue(w)) > ReinterpretAsF64(w.Previous)); case ComparisonOperator.GreaterThanEqual: return watchList.Where(w => { - var val = ReinterpretAsF32(GetValue(w)); - var prev = ReinterpretAsF32(w.Previous); + var val = ReinterpretAsF64(GetValue(w)); + var prev = ReinterpretAsF64(w.Previous); return val > prev || val.HawkFloatEquality(prev); }); case ComparisonOperator.LessThan: - return watchList.Where(w => ReinterpretAsF32(GetValue(w)) < ReinterpretAsF32(w.Previous)); + return watchList.Where(w => ReinterpretAsF64(GetValue(w)) < ReinterpretAsF64(w.Previous)); case ComparisonOperator.LessThanEqual: return watchList.Where(w => { - var val = ReinterpretAsF32(GetValue(w)); - var prev = ReinterpretAsF32(w.Previous); + var val = ReinterpretAsF64(GetValue(w)); + var prev = ReinterpretAsF64(w.Previous); return val < prev || val.HawkFloatEquality(prev); }); case ComparisonOperator.DifferentBy: - if (DifferentBy is not uint differentBy) throw new InvalidOperationException(); - var differentByF = ReinterpretAsF32(differentBy); - return watchList.Where(w => Math.Abs(ReinterpretAsF32(GetValue(w)) - ReinterpretAsF32(w.Previous)) - .HawkFloatEquality(differentByF)); + if (DifferentBy is not ulong differentBy) throw new InvalidOperationException(); + return watchList.Where(w => DifferByExactly( + difference: differentBy, + ReinterpretAsF64(GetValue(w)), + ReinterpretAsF64(w.Previous))); } } private IEnumerable CompareSpecificValue(IEnumerable watchList) { - if (CompareValue is not uint compareValue) throw new InvalidOperationException(); + if (CompareValue is not ulong compareValue) throw new InvalidOperationException(); if (_settings.Type is not WatchDisplayType.Float) { switch (Operator) @@ -469,46 +502,50 @@ private IEnumerable CompareSpecificValue(IEnumerable wat case ComparisonOperator.LessThanEqual: return watchList.Where(w => SignExtendAsNeeded(GetValue(w)) <= SignExtendAsNeeded(compareValue)); case ComparisonOperator.DifferentBy: - if (DifferentBy is not uint differentBy) throw new InvalidOperationException(); - return watchList.Where(w => - differentBy == Math.Abs(SignExtendAsNeeded(GetValue(w)) - SignExtendAsNeeded(compareValue))); + if (DifferentBy is not ulong differentBy) throw new InvalidOperationException(); + return watchList.Where(w => DifferByExactly( + difference: differentBy, + SignExtendAsNeeded(GetValue(w)), + SignExtendAsNeeded(compareValue))); } } - var compareValueF = ReinterpretAsF32(compareValue); + var compareValueF = ReinterpretAsF64(compareValue); switch (Operator) { default: case ComparisonOperator.Equal: - return watchList.Where(w => ReinterpretAsF32(GetValue(w)).HawkFloatEquality(compareValueF)); + return watchList.Where(w => ReinterpretAsF64(GetValue(w)).HawkFloatEquality(compareValueF)); case ComparisonOperator.NotEqual: - return watchList.Where(w => !ReinterpretAsF32(GetValue(w)).HawkFloatEquality(compareValueF)); + return watchList.Where(w => !ReinterpretAsF64(GetValue(w)).HawkFloatEquality(compareValueF)); case ComparisonOperator.GreaterThan: - return watchList.Where(w => ReinterpretAsF32(GetValue(w)) > compareValueF); + return watchList.Where(w => ReinterpretAsF64(GetValue(w)) > compareValueF); case ComparisonOperator.GreaterThanEqual: return watchList.Where(w => { - var val = ReinterpretAsF32(GetValue(w)); + var val = ReinterpretAsF64(GetValue(w)); return val > compareValueF || val.HawkFloatEquality(compareValueF); }); case ComparisonOperator.LessThan: - return watchList.Where(w => ReinterpretAsF32(GetValue(w)) < compareValueF); + return watchList.Where(w => ReinterpretAsF64(GetValue(w)) < compareValueF); case ComparisonOperator.LessThanEqual: return watchList.Where(w => { - var val = ReinterpretAsF32(GetValue(w)); + var val = ReinterpretAsF64(GetValue(w)); return val < compareValueF || val.HawkFloatEquality(compareValueF); }); case ComparisonOperator.DifferentBy: - if (DifferentBy is not uint differentBy) throw new InvalidOperationException(); - var differentByF = ReinterpretAsF32(differentBy); - return watchList.Where(w => Math.Abs(ReinterpretAsF32(GetValue(w)) - compareValueF) - .HawkFloatEquality(differentByF)); + if (DifferentBy is not ulong differentBy) throw new InvalidOperationException(); + return watchList.Where(w => DifferByExactly( + difference: differentBy, + ReinterpretAsF64(GetValue(w)), + compareValueF)); } } private IEnumerable CompareSpecificAddress(IEnumerable watchList) { - if (CompareValue is not uint compareValue) throw new InvalidOperationException(); + if (CompareValue is not ulong compareValue0) throw new InvalidOperationException(); + var compareValue = compareValue0 > long.MaxValue ? -1L : unchecked((long) compareValue0); switch (Operator) { default: @@ -525,15 +562,16 @@ private IEnumerable CompareSpecificAddress(IEnumerable w case ComparisonOperator.LessThanEqual: return watchList.Where(w => w.Address <= compareValue); case ComparisonOperator.DifferentBy: - if (DifferentBy is not uint differentBy) throw new InvalidOperationException(); - return watchList.Where(w => Math.Abs(w.Address - compareValue) == differentBy); + if (DifferentBy is not ulong differentBy) throw new InvalidOperationException(); + return watchList.Where(w => DifferByExactly(difference: differentBy, w.Address, compareValue)); } } private IEnumerable CompareChanges(IEnumerable watchList) { if (!_settings.IsDetailed()) throw new InvalidCastException(); //TODO matches previous behaviour; was this intended to skip processing? --yoshi - if (CompareValue is not uint compareValue) throw new InvalidOperationException(); + if (CompareValue is not ulong compareValue0) throw new InvalidOperationException(); + var compareValue = compareValue0 > int.MaxValue ? -1L : unchecked((int) compareValue0); switch (Operator) { default: @@ -562,16 +600,17 @@ private IEnumerable CompareChanges(IEnumerable watchList .Cast() .Where(w => w.ChangeCount <= compareValue); case ComparisonOperator.DifferentBy: - if (DifferentBy is not uint differentBy) throw new InvalidOperationException(); + if (DifferentBy is not ulong differentBy) throw new InvalidOperationException(); return watchList .Cast() - .Where(w => Math.Abs(w.ChangeCount - compareValue) == differentBy); + .Where(w => DifferByExactly(difference: differentBy, w.ChangeCount, compareValue)); } } private IEnumerable CompareDifference(IEnumerable watchList) { - if (CompareValue is not uint compareValue) throw new InvalidOperationException(); + if (CompareValue is not ulong compareValue0) throw new InvalidOperationException(); + var compareValue = unchecked((long) compareValue0); if (_settings.Type is not WatchDisplayType.Float) { switch (Operator) @@ -590,73 +629,66 @@ private IEnumerable CompareDifference(IEnumerable watchL case ComparisonOperator.LessThanEqual: return watchList.Where(w => SignExtendAsNeeded(GetValue(w)) - SignExtendAsNeeded(w.Previous) <= compareValue); case ComparisonOperator.DifferentBy: - if (DifferentBy is not uint differentBy) throw new InvalidOperationException(); - return watchList.Where(w => - differentBy == Math.Abs(SignExtendAsNeeded(GetValue(w)) - SignExtendAsNeeded(w.Previous) - compareValue)); + if (DifferentBy is not ulong differentBy) throw new InvalidOperationException(); + return watchList.Where(w => DifferByExactly( + difference: differentBy, + SignExtendAsNeeded(GetValue(w)) - SignExtendAsNeeded(w.Previous), + compareValue)); } } - var compareValueF = ReinterpretAsF32(compareValue); + var compareValueF = ReinterpretAsF64(compareValue0); switch (Operator) { default: case ComparisonOperator.Equal: - return watchList.Where(w => (ReinterpretAsF32(GetValue(w)) - ReinterpretAsF32(w.Previous)).HawkFloatEquality(compareValueF)); + return watchList.Where(w => (ReinterpretAsF64(GetValue(w)) - ReinterpretAsF64(w.Previous)).HawkFloatEquality(compareValueF)); case ComparisonOperator.NotEqual: - return watchList.Where(w => !(ReinterpretAsF32(GetValue(w)) - ReinterpretAsF32(w.Previous)).HawkFloatEquality(compareValueF)); + return watchList.Where(w => !(ReinterpretAsF64(GetValue(w)) - ReinterpretAsF64(w.Previous)).HawkFloatEquality(compareValueF)); case ComparisonOperator.GreaterThan: - return watchList.Where(w => ReinterpretAsF32(GetValue(w)) - ReinterpretAsF32(w.Previous) > compareValueF); + return watchList.Where(w => ReinterpretAsF64(GetValue(w)) - ReinterpretAsF64(w.Previous) > compareValueF); case ComparisonOperator.GreaterThanEqual: return watchList.Where(w => { - var diff = ReinterpretAsF32(GetValue(w)) - ReinterpretAsF32(w.Previous); + var diff = ReinterpretAsF64(GetValue(w)) - ReinterpretAsF64(w.Previous); return diff > compareValueF || diff.HawkFloatEquality(compareValueF); }); case ComparisonOperator.LessThan: - return watchList.Where(w => ReinterpretAsF32(GetValue(w)) - ReinterpretAsF32(w.Previous) < compareValueF); + return watchList.Where(w => ReinterpretAsF64(GetValue(w)) - ReinterpretAsF64(w.Previous) < compareValueF); case ComparisonOperator.LessThanEqual: return watchList.Where(w => { - var diff = ReinterpretAsF32(GetValue(w)) - ReinterpretAsF32(w.Previous); + var diff = ReinterpretAsF64(GetValue(w)) - ReinterpretAsF64(w.Previous); return diff < compareValueF || diff.HawkFloatEquality(compareValueF); }); case ComparisonOperator.DifferentBy: - if (DifferentBy is not uint differentBy) throw new InvalidOperationException(); - var differentByF = ReinterpretAsF32(differentBy); - return watchList.Where(w => Math.Abs(ReinterpretAsF32(GetValue(w)) - ReinterpretAsF32(w.Previous) - compareValueF) - .HawkFloatEquality(differentByF)); + if (DifferentBy is not ulong differentBy) throw new InvalidOperationException(); + return watchList.Where(w => DifferByExactly( + difference: differentBy, + ReinterpretAsF64(GetValue(w)) - ReinterpretAsF64(w.Previous), + compareValueF)); } } - private long SignExtendAsNeeded(uint val) + private long SignExtendAsNeeded(ulong val) { - if (_settings.Type != WatchDisplayType.Signed) - { - return val; - } - + if (_settings.Type is not WatchDisplayType.Signed) return unchecked((long) val); return _settings.Size switch { WatchSize.Byte => (sbyte) val, WatchSize.Word => (short) val, WatchSize.DWord => (int) val, + WatchSize.QWord => (long) val, _ => (sbyte) val, }; } - private uint GetValue(IMiniWatch watch) + private ulong GetValue(IMiniWatch watch) { if (watch is IMiniWatchDetails detailedWatch) { return detailedWatch.Current; } - - return _settings.Size switch - { - WatchSize.Byte => MiniByteWatch.GetByte(watch.Address, Domain), - WatchSize.Word => MiniWordWatch.GetUshort(watch.Address, Domain, _settings.BigEndian), - WatchSize.DWord => MiniDWordWatch.GetUint(watch.Address, Domain, _settings.BigEndian), - _ => MiniByteWatch.GetByte(watch.Address, Domain), - }; + return watch.GetValue(Domain, bigEndian: _settings.BigEndian); } private bool CanDoCompareType(Compare compareType) diff --git a/src/BizHawk.Client.Common/tools/Watch/ByteWatch.cs b/src/BizHawk.Client.Common/tools/Watch/ByteWatch.cs index 93b67aae859..e1f19c0e8c1 100644 --- a/src/BizHawk.Client.Common/tools/Watch/ByteWatch.cs +++ b/src/BizHawk.Client.Common/tools/Watch/ByteWatch.cs @@ -146,12 +146,14 @@ public string FormatValue(byte val) /// /// Get the maximum possible value /// - public override uint MaxValue => byte.MaxValue; + public override ulong MaxValue + => byte.MaxValue; /// /// Get the current value /// - public override int Value => GetByte(); + public override long Value + => GetByte(); /// /// Get a string representation of the current value @@ -161,7 +163,8 @@ public string FormatValue(byte val) /// /// Get the previous value /// - public override uint Previous => _previous; + public override ulong Previous + => _previous; /// /// Get a string representation of the previous value diff --git a/src/BizHawk.Client.Common/tools/Watch/DWordWatch.cs b/src/BizHawk.Client.Common/tools/Watch/DWordWatch.cs index 0a058dbdaeb..14a9b3a423d 100644 --- a/src/BizHawk.Client.Common/tools/Watch/DWordWatch.cs +++ b/src/BizHawk.Client.Common/tools/Watch/DWordWatch.cs @@ -170,12 +170,14 @@ string FormatBinary() /// /// Get the maximum possible value /// - public override uint MaxValue => uint.MaxValue; + public override ulong MaxValue + => uint.MaxValue; /// /// Get the current value /// - public override int Value => (int)GetDWord(); + public override long Value + => GetDWord(); /// /// Get a string representation of the current value @@ -185,7 +187,8 @@ string FormatBinary() /// /// Get the previous value /// - public override uint Previous => _previous; + public override ulong Previous + => _previous; /// /// Get a string representation of the previous value diff --git a/src/BizHawk.Client.Common/tools/Watch/QWordWatch.cs b/src/BizHawk.Client.Common/tools/Watch/QWordWatch.cs new file mode 100644 index 00000000000..5177fb9a90c --- /dev/null +++ b/src/BizHawk.Client.Common/tools/Watch/QWordWatch.cs @@ -0,0 +1,188 @@ +using System.Collections.Generic; +using System.Globalization; +using BizHawk.Common.NumberExtensions; +using BizHawk.Emulation.Common; + +namespace BizHawk.Client.Common +{ + /// + /// This class holds a quad word (64 bits) + /// + public sealed class QWordWatch : Watch + { + /// + /// Gets a list of for a + /// + public static readonly IReadOnlyList ValidTypes = [ + WatchDisplayType.Unsigned, + WatchDisplayType.Signed, + WatchDisplayType.Hex, + WatchDisplayType.Binary, + WatchDisplayType.Float, + ]; + + private ulong _value; + + private ulong _previous; + + /// + /// Initializes a new instance of the class + /// + /// where you want to track + /// The address you want to track + /// selected format for displaying the value + /// Specify the endianess. true for big endian + /// A custom note about the + /// Current value + /// Previous value + /// How many times value has changed + /// Occurs when a is incompatible with + internal QWordWatch( + MemoryDomain domain, + long address, + WatchDisplayType type, + bool bigEndian, + string note, + ulong value, + ulong previous, + int changeCount) + : base(domain, address, WatchSize.QWord, type, bigEndian: bigEndian, note) + { + _value = value is 0 ? GetQWord() : value; + _previous = previous; + ChangeCount = changeCount; + } + + /// + /// Get a list of that can be used for a + /// + /// An enumeration that contains all valid + public override IReadOnlyList AvailableTypes() + => ValidTypes; + + /// + /// Reset the previous value; set it to the current one + /// + public override void ResetPrevious() + => _previous = GetQWord(); + + /// + /// Try to sets the value into the + /// at the current address + /// + /// Value to set + /// True if value successfully sets; otherwise, false + public override bool Poke(string value) + { + try + { + PokeQWord(Type switch + { + WatchDisplayType.Unsigned => ulong.Parse(value), + WatchDisplayType.Signed => (ulong) long.Parse(value), + WatchDisplayType.Hex => ulong.Parse(value, NumberStyles.HexNumber), + WatchDisplayType.Float => NumberExtensions.ReinterpretAsUInt64(double.Parse(value, NumberFormatInfo.InvariantInfo)), + WatchDisplayType.Binary => Convert.ToUInt64(value, fromBase: 2), + _ => 0, + }); + return true; + } + catch + { + return false; + } + } + + /// + /// Update the Watch (read it from + /// + public override void Update(PreviousType previousType) + { + switch (previousType) + { + case PreviousType.Original: + return; + case PreviousType.LastChange: + var nextValue = GetQWord(); + if (nextValue != _value) + { + _previous = nextValue; + ChangeCount++; + } + _value = nextValue; + break; + case PreviousType.LastFrame: + _previous = _value; + _value = GetQWord(); + if (_value != Previous) ChangeCount++; + break; + } + } + + // TODO: Implements IFormattable + public string FormatValue(ulong val) + { + string FormatFloat() + => NumberExtensions.ReinterpretAsF64(val).ToString(NumberFormatInfo.InvariantInfo); + string FormatBinary() + { + var str = Convert.ToString(unchecked((long) val), toBase: 2).PadLeft(64, '0'); + for (var i = 60; i > 0; i -= 4) str = str.Insert(i, " "); + return str; + } + return Type switch + { + _ when !IsValid => "-", + WatchDisplayType.Unsigned => val.ToString(), + WatchDisplayType.Signed => ((long) val).ToString(), + WatchDisplayType.Hex => $"{val:X16}", + WatchDisplayType.Float => FormatFloat(), + WatchDisplayType.Binary => FormatBinary(), + _ => val.ToString(), + }; + } + + /// + /// Get a string representation of difference + /// between current value and the previous one + /// + public override string Diff + => $"{unchecked((long) _value - (long) _previous):+#;-#;0}"; + + /// + /// Returns true if the Watch is valid, false otherwise + /// + public override bool IsValid + => Domain.Size is 0 || Address < (Domain.Size - (sizeof(ulong) - 1)); + + /// + /// Get the maximum possible value + /// + public override ulong MaxValue + => ulong.MaxValue; + + /// + /// Get the current value + /// + public override long Value + => unchecked((long) GetQWord()); + + /// + /// Get a string representation of the current value + /// + public override string ValueString + => FormatValue(GetQWord()); + + /// + /// Get the previous value + /// + public override ulong Previous + => _previous; + + /// + /// Get a string representation of the previous value + /// + public override string PreviousStr + => FormatValue(_previous); + } +} diff --git a/src/BizHawk.Client.Common/tools/Watch/SeparatorWatch.cs b/src/BizHawk.Client.Common/tools/Watch/SeparatorWatch.cs index 7bec65af711..c036fa6f427 100644 --- a/src/BizHawk.Client.Common/tools/Watch/SeparatorWatch.cs +++ b/src/BizHawk.Client.Common/tools/Watch/SeparatorWatch.cs @@ -41,12 +41,14 @@ public override IReadOnlyList AvailableTypes() /// /// Ignore that stuff /// - public override int Value => 0; + public override long Value + => default; /// /// Ignore that stuff /// - public override uint Previous => 0; + public override ulong Previous + => default; /// /// Ignore that stuff @@ -104,7 +106,8 @@ public override void ResetPrevious() /// /// Ignore that stuff /// - public override uint MaxValue => 0; + public override ulong MaxValue + => default; /// /// Ignore that stuff diff --git a/src/BizHawk.Client.Common/tools/Watch/Watch.cs b/src/BizHawk.Client.Common/tools/Watch/Watch.cs index 32c81f9a570..aa18034c20e 100644 --- a/src/BizHawk.Client.Common/tools/Watch/Watch.cs +++ b/src/BizHawk.Client.Common/tools/Watch/Watch.cs @@ -10,7 +10,7 @@ namespace BizHawk.Client.Common { /// /// This class holds a watch i.e. something inside a identified by an address - /// with a specific size (8, 16 or 32bits). + /// with a specific size (8, 16, 32, or 64 bits). /// This is an abstract class /// [DebuggerDisplay("Note={Notes}, Value={ValueString}")] @@ -117,7 +117,8 @@ public static Watch FromString(string line, IMemoryDomains domains) /// /// Generates a new instance - /// Can be either , , or + /// Can be either , , , , + /// or /// /// The where you want to watch /// The address into the @@ -129,7 +130,16 @@ public static Watch FromString(string line, IMemoryDomains domains) /// Previous value /// Number of changes occurs in current /// New instance. True type is depending of size parameter - public static Watch GenerateWatch(MemoryDomain domain, long address, WatchSize size, WatchDisplayType type, bool bigEndian, string note = "", long value = 0, long prev = 0, int changeCount = 0) + public static Watch GenerateWatch( + MemoryDomain domain, + long address, + WatchSize size, + WatchDisplayType type, + bool bigEndian, + string note = "", + long value = 0, + long prev = 0, + int changeCount = 0) { return size switch { @@ -137,6 +147,7 @@ public static Watch GenerateWatch(MemoryDomain domain, long address, WatchSize s WatchSize.Byte => new ByteWatch(domain, address, type, bigEndian, note, (byte) value, (byte) prev, changeCount), WatchSize.Word => new WordWatch(domain, address, type, bigEndian, note, (ushort) value, (ushort) prev, changeCount), WatchSize.DWord => new DWordWatch(domain, address, type, bigEndian, note, (uint) value, (uint) prev, changeCount), + WatchSize.QWord => new QWordWatch(domain, address, type, bigEndian, note, (ulong) value, (ulong) prev, changeCount), _ => SeparatorWatch.NewSeparatorWatch(note), }; } @@ -285,6 +296,9 @@ protected uint GetDWord() : 0; } + protected ulong GetQWord() + => IsValid ? _domain.PeekUlong(Address, BigEndian) : 0UL; + protected void PokeByte(byte val) { if (IsValid) @@ -309,6 +323,11 @@ protected void PokeDWord(uint val) } } + protected void PokeQWord(ulong val) + { + if (IsValid) _domain.PokeUlong(Address, val, BigEndian); + } + /// /// Sets the number of changes to 0 /// @@ -430,12 +449,12 @@ public override string ToString() /// /// Gets the maximum possible value /// - public abstract uint MaxValue { get; } + public abstract ulong MaxValue { get; } /// /// Gets the current value /// - public abstract int Value { get; } + public abstract long Value { get; } /// /// Gets a string representation of the current value @@ -458,7 +477,7 @@ public override string ToString() /// /// Gets the previous value /// - public abstract uint Previous { get; } + public abstract ulong Previous { get; } /// /// Gets a string representation of the previous value @@ -581,6 +600,7 @@ public char SizeAsChar WatchSize.Byte => 'b', WatchSize.Word => 'w', WatchSize.DWord => 'd', + WatchSize.QWord => 'q', _ => 'S', }; } @@ -594,6 +614,7 @@ public static WatchSize SizeFromChar(char c) 'b' => WatchSize.Byte, 'w' => WatchSize.Word, 'd' => WatchSize.DWord, + 'q' => WatchSize.QWord, _ => WatchSize.Separator, }; } @@ -635,7 +656,7 @@ public static WatchDisplayType DisplayTypeFromChar(char c) }; } - public bool IsSplittable => Size is WatchSize.Word or WatchSize.DWord - && Type is WatchDisplayType.Hex or WatchDisplayType.Binary; + public bool IsSplittable + => Size >= WatchSize.Word && Type is WatchDisplayType.Hex or WatchDisplayType.Binary; } } diff --git a/src/BizHawk.Client.Common/tools/Watch/WatchSize.cs b/src/BizHawk.Client.Common/tools/Watch/WatchSize.cs index f63861c1953..15adc967a0b 100644 --- a/src/BizHawk.Client.Common/tools/Watch/WatchSize.cs +++ b/src/BizHawk.Client.Common/tools/Watch/WatchSize.cs @@ -23,6 +23,12 @@ public enum WatchSize : int /// DWord = 4, + /// + /// 8 bytes (64 bits) + /// Use this for + /// + QWord = 8, + /// /// Special case used for a separator in ram tools /// Use this for diff --git a/src/BizHawk.Client.Common/tools/Watch/WordWatch.cs b/src/BizHawk.Client.Common/tools/Watch/WordWatch.cs index bb4d6355b1d..a64183aaff1 100644 --- a/src/BizHawk.Client.Common/tools/Watch/WordWatch.cs +++ b/src/BizHawk.Client.Common/tools/Watch/WordWatch.cs @@ -152,12 +152,14 @@ public string FormatValue(ushort val) /// /// Get the maximum possible value /// - public override uint MaxValue => ushort.MaxValue; + public override ulong MaxValue + => ushort.MaxValue; /// /// Gets the current value /// - public override int Value => GetWord(); + public override long Value + => GetWord(); /// /// Get a string representation of the current value @@ -167,7 +169,8 @@ public string FormatValue(ushort val) /// /// Get the previous value /// - public override uint Previous => _previous; + public override ulong Previous + => _previous; /// /// Get a string representation of the previous value diff --git a/src/BizHawk.Client.EmuHawk/tools/Watch/WatchValueBox.cs b/src/BizHawk.Client.EmuHawk/tools/Watch/WatchValueBox.cs index 2feec1386f7..f4da21bd686 100644 --- a/src/BizHawk.Client.EmuHawk/tools/Watch/WatchValueBox.cs +++ b/src/BizHawk.Client.EmuHawk/tools/Watch/WatchValueBox.cs @@ -3,6 +3,7 @@ using System.Windows.Forms; using BizHawk.Client.Common; using BizHawk.Client.EmuHawk.CustomControls; +using BizHawk.Common.CollectionExtensions; using BizHawk.Common.NumberExtensions; using BizHawk.Common.StringExtensions; @@ -31,18 +32,17 @@ public WatchSize ByteSize if (changed) { SetMaxLength(); - - var isTypeCompatible = value switch + var validTypes = value switch { - WatchSize.Byte => ByteWatch.ValidTypes.Any(t => t == _type), - WatchSize.Word => WordWatch.ValidTypes.Any(t => t == _type), - WatchSize.DWord => DWordWatch.ValidTypes.Any(t => t == _type), - _ => false, + WatchSize.Byte => ByteWatch.ValidTypes, + WatchSize.Word => WordWatch.ValidTypes, + WatchSize.DWord => DWordWatch.ValidTypes, + WatchSize.QWord => QWordWatch.ValidTypes, + _ => [ ], }; - - if (!isTypeCompatible) + if (!validTypes.Contains(_type)) { - _type = WatchDisplayType.Unsigned; + _type = validTypes is [ var first, .. ] ? first : WatchDisplayType.Unsigned; } ResetText(); @@ -67,6 +67,7 @@ public WatchDisplayType Type { WatchSize.Word => ushort.MaxValue, WatchSize.DWord => uint.MaxValue, +// WatchSize.QWord => ulong.MaxValue, _ => byte.MaxValue, }; @@ -75,6 +76,7 @@ public WatchDisplayType Type { WatchSize.Word => short.MaxValue, WatchSize.DWord => int.MaxValue, +// WatchSize.QWord => long.MaxValue, _ => sbyte.MaxValue, }; @@ -83,6 +85,7 @@ public WatchDisplayType Type { WatchSize.Word => short.MinValue, WatchSize.DWord => int.MinValue, +// WatchSize.QWord => long.MinValue, _ => sbyte.MinValue, }; @@ -145,6 +148,7 @@ private void SetMaxLength() WatchSize.Byte => 8, WatchSize.Word => 16, WatchSize.DWord => 32, + WatchSize.QWord => 8 * sizeof(ulong), _ => 8, }; break; @@ -154,6 +158,7 @@ private void SetMaxLength() WatchSize.Byte => 2, WatchSize.Word => 4, WatchSize.DWord => 8, + WatchSize.QWord => 2 * sizeof(ulong), _ => 2, }; break; @@ -163,6 +168,7 @@ private void SetMaxLength() WatchSize.Byte => 4, WatchSize.Word => 6, WatchSize.DWord => 11, + WatchSize.QWord => long.MinValue.ToString(CultureInfo.InvariantCulture).Length, //TODO hardcode _ => 4, }; break; @@ -172,6 +178,7 @@ private void SetMaxLength() WatchSize.Byte => 3, WatchSize.Word => 5, WatchSize.DWord => 10, + WatchSize.QWord => ulong.MaxValue.ToString(CultureInfo.InvariantCulture).Length, //TODO hardcode _ => 3, }; break; diff --git a/src/BizHawk.Common/Extensions/NumberExtensions.cs b/src/BizHawk.Common/Extensions/NumberExtensions.cs index 2d459f999f7..ea12847010f 100644 --- a/src/BizHawk.Common/Extensions/NumberExtensions.cs +++ b/src/BizHawk.Common/Extensions/NumberExtensions.cs @@ -252,12 +252,20 @@ public static void RotateRightU8(ref byte b, int shift) /// don't use this in cores without picking a suitable ε public static bool HawkFloatEquality(this float f, float other, float ε = ReallySmallNumber) => Math.Abs(other - f) < ε; -#pragma warning disable RCS1224 // don't want extension on nonspecific `float`/`uint` +#pragma warning disable RCS1224 // don't want extension on nonspecific `double`/`float`/`uint`/`ulong` /// Reinterprets the byte representation of as a float public static float ReinterpretAsF32(uint value) => Unsafe.As(ref value); + /// Reinterprets the byte representation of as a double + public static double ReinterpretAsF64(ulong value) + => Unsafe.As(ref value); + /// Reinterprets the byte representation of as a uint public static uint ReinterpretAsUInt32(float value) => Unsafe.As(ref value); + + /// Reinterprets the byte representation of as a ulong + public static ulong ReinterpretAsUInt64(double value) + => Unsafe.As(ref value); #pragma warning restore RCS1224 } } diff --git a/src/BizHawk.Emulation.Common/Base Implementations/MemoryDomain.cs b/src/BizHawk.Emulation.Common/Base Implementations/MemoryDomain.cs index 66c1a56b4f9..96b7297fbf5 100644 --- a/src/BizHawk.Emulation.Common/Base Implementations/MemoryDomain.cs +++ b/src/BizHawk.Emulation.Common/Base Implementations/MemoryDomain.cs @@ -60,6 +60,24 @@ public virtual uint PeekUint(long addr, bool bigEndian) : BinaryPrimitives.ReadUInt32LittleEndian(scratch); } + public virtual ulong PeekUlong(long addr, bool bigEndian) + { + ReadOnlySpan scratch = stackalloc byte[] + { + PeekByte(addr), + PeekByte(addr + 1), + PeekByte(addr + 2), + PeekByte(addr + 3), + PeekByte(addr + 4), + PeekByte(addr + 5), + PeekByte(addr + 6), + PeekByte(addr + 7), + }; + return bigEndian + ? BinaryPrimitives.ReadUInt64BigEndian(scratch) + : BinaryPrimitives.ReadUInt64LittleEndian(scratch); + } + public virtual void PokeUshort(long addr, ushort val, bool bigEndian) { if (bigEndian) @@ -85,6 +103,14 @@ public virtual void PokeUint(long addr, uint val, bool bigEndian) PokeByte(addr + 3, scratch[3]); } + public virtual void PokeUlong(long addr, ulong val, bool bigEndian) + { + Span scratch = stackalloc byte[8]; + if (bigEndian) BinaryPrimitives.WriteUInt64BigEndian(scratch, val); + else BinaryPrimitives.WriteUInt64LittleEndian(scratch, val); + for (var i = 0; i < scratch.Length; i++) PokeByte(addr + i, scratch[i]); + } + public virtual void BulkPeekByte(Range addresses, byte[] values) { if (addresses is null) throw new ArgumentNullException(paramName: nameof(addresses)); @@ -152,6 +178,8 @@ public virtual void BulkPeekUint(Range addresses, bool bigEndian, uint[] v } } + //TODO BulkPeekUlong? + public virtual void SendCheatToCore(int addr, byte value, int compare, int compare_type) { } ///