diff --git a/src/BizHawk.Client.Common/tools/Cheat.cs b/src/BizHawk.Client.Common/tools/Cheat.cs index fc851e8811..9f267134ba 100644 --- a/src/BizHawk.Client.Common/tools/Cheat.cs +++ b/src/BizHawk.Client.Common/tools/Cheat.cs @@ -105,6 +105,7 @@ namespace BizHawk.Client.Common 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, }; @@ -120,6 +121,7 @@ namespace BizHawk.Client.Common 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, }; @@ -186,6 +188,9 @@ namespace BizHawk.Client.Common case WatchSize.DWord: _watch.Poke(((DWordWatch)_watch).FormatValue((uint)_val)); break; + case WatchSize.QWord: + _watch.Poke(((QWordWatch) _watch).FormatValue(unchecked((ulong) _val))); + break; } } @@ -237,6 +242,8 @@ namespace BizHawk.Client.Common 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); } } diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/Extensions.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/Extensions.cs index 14ff056127..6d720bd37b 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/Extensions.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/Extensions.cs @@ -21,6 +21,11 @@ namespace BizHawk.Client.Common.RamSearchEngine ? 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 @@ namespace BizHawk.Client.Common.RamSearchEngine 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 86369cc768..7045637359 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatch.cs @@ -79,4 +79,16 @@ namespace BizHawk.Client.Common.RamSearchEngine protected override bool IsValid(long address, MemoryDomain domain) => 0L <= address && address <= domain.Size - sizeof(uint); } + + internal class MiniQWordWatch : MiniWatchBase + { + public MiniQWordWatch(MemoryDomain domain, long addr, bool bigEndian) + : base(addr: addr, prevValue: domain.PeekUint(addr, bigEndian: bigEndian)) {} + + protected override ulong GetValueInner(long address, MemoryDomain domain, bool bigEndian) + => domain.PeekUlong(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 d833e573d1..6ecfe0a455 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/IMiniWatchDetails.cs @@ -126,4 +126,41 @@ namespace BizHawk.Client.Common.RamSearchEngine 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 = 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 void ClearChangeCount() => ChangeCount = 0; + } } diff --git a/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs b/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs index 214e99d1ea..398dab9237 100644 --- a/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs +++ b/src/BizHawk.Client.Common/tools/RamSearchEngine/RamSearchEngine.cs @@ -109,6 +109,22 @@ namespace BizHawk.Client.Common.RamSearchEngine } } 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; } } @@ -284,6 +300,7 @@ namespace BizHawk.Client.Common.RamSearchEngine WatchSize.Byte => addresses.ToBytes(_settings), WatchSize.Word => addresses.ToWords(_settings), WatchSize.DWord => addresses.ToDWords(_settings), + WatchSize.QWord => addresses.ToQWords(_settings), _ => addresses.ToBytes(_settings), }; @@ -302,6 +319,8 @@ namespace BizHawk.Client.Common.RamSearchEngine 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, }; @@ -325,6 +344,15 @@ namespace BizHawk.Client.Common.RamSearchEngine 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; } } } @@ -649,6 +677,7 @@ namespace BizHawk.Client.Common.RamSearchEngine WatchSize.Byte => (sbyte) val, WatchSize.Word => (short) val, WatchSize.DWord => (int) val, + WatchSize.QWord => (long) val, _ => (sbyte) val, }; } 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 0000000000..c96013249e --- /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 + /// How you you want to display the value See + /// 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(float.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/Watch.cs b/src/BizHawk.Client.Common/tools/Watch/Watch.cs index b4e171b060..2cc144196f 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 @@ namespace BizHawk.Client.Common /// /// Generates a new instance - /// Can be either , , or + /// Can be either , , , , + /// or /// /// The where you want to watch /// The address into the @@ -146,6 +147,7 @@ namespace BizHawk.Client.Common 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), }; } @@ -294,6 +296,9 @@ namespace BizHawk.Client.Common : 0; } + protected ulong GetQWord() + => IsValid ? _domain.PeekUlong(Address, BigEndian) : 0UL; + protected void PokeByte(byte val) { if (IsValid) @@ -318,6 +323,11 @@ namespace BizHawk.Client.Common } } + protected void PokeQWord(ulong val) + { + if (IsValid) _domain.PokeUlong(Address, val, BigEndian); + } + /// /// Sets the number of changes to 0 /// @@ -590,6 +600,7 @@ namespace BizHawk.Client.Common WatchSize.Byte => 'b', WatchSize.Word => 'w', WatchSize.DWord => 'd', + WatchSize.QWord => 'q', _ => 'S', }; } @@ -603,6 +614,7 @@ namespace BizHawk.Client.Common 'b' => WatchSize.Byte, 'w' => WatchSize.Word, 'd' => WatchSize.DWord, + 'q' => WatchSize.QWord, _ => WatchSize.Separator, }; } @@ -644,7 +656,7 @@ namespace BizHawk.Client.Common }; } - 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 f63861c195..15adc967a0 100644 --- a/src/BizHawk.Client.Common/tools/Watch/WatchSize.cs +++ b/src/BizHawk.Client.Common/tools/Watch/WatchSize.cs @@ -23,6 +23,12 @@ /// DWord = 4, + /// + /// 8 bytes (64 bits) + /// Use this for + /// + QWord = 8, + /// /// Special case used for a separator in ram tools /// Use this for