Add 64-bit-wide watches

This commit is contained in:
YoshiRulz 2025-06-15 04:37:26 +10:00
parent d34f8a3fa7
commit 17bfab9a2d
No known key found for this signature in database
GPG Key ID: C4DE31C245353FB7
8 changed files with 306 additions and 4 deletions

View File

@ -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);
}
}

View File

@ -21,6 +21,11 @@ namespace BizHawk.Client.Common.RamSearchEngine
? addresses.ToDetailedDWords(settings.Domain, settings.BigEndian)
: addresses.ToDWords(settings.Domain, settings.BigEndian);
public static IEnumerable<IMiniWatch> ToQWords(this IEnumerable<long> addresses, SearchEngineSettings settings)
=> settings.IsDetailed()
? addresses.ToDetailedQWords(settings.Domain, settings.BigEndian)
: addresses.ToQWords(settings.Domain, settings.BigEndian);
private static IEnumerable<IMiniWatch> ToBytes(this IEnumerable<long> addresses, MemoryDomain domain)
=> addresses.Select(a => new MiniByteWatch(domain, a));
@ -38,5 +43,11 @@ namespace BizHawk.Client.Common.RamSearchEngine
private static IEnumerable<IMiniWatch> ToDetailedDWords(this IEnumerable<long> addresses, MemoryDomain domain, bool bigEndian)
=> addresses.Select(a => new MiniDWordWatchDetailed(domain, a, bigEndian));
private static IEnumerable<IMiniWatch> ToQWords(this IEnumerable<long> addresses, MemoryDomain domain, bool bigEndian)
=> addresses.Select(a => new MiniQWordWatch(domain, a, bigEndian));
private static IEnumerable<IMiniWatch> ToDetailedQWords(this IEnumerable<long> addresses, MemoryDomain domain, bool bigEndian)
=> addresses.Select(a => new MiniQWordWatchDetailed(domain, a, bigEndian));
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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,
};
}

View File

@ -0,0 +1,188 @@
using System.Collections.Generic;
using System.Globalization;
using BizHawk.Common.NumberExtensions;
using BizHawk.Emulation.Common;
namespace BizHawk.Client.Common
{
/// <summary>
/// This class holds a quad word (64 bits) <see cref="Watch"/>
/// </summary>
public sealed class QWordWatch : Watch
{
/// <summary>
/// Gets a list of <see cref="WatchDisplayType"/> for a <see cref="QWordWatch"/>
/// </summary>
public static readonly IReadOnlyList<WatchDisplayType> ValidTypes = [
WatchDisplayType.Unsigned,
WatchDisplayType.Signed,
WatchDisplayType.Hex,
WatchDisplayType.Binary,
WatchDisplayType.Float,
];
private ulong _value;
private ulong _previous;
/// <summary>
/// Initializes a new instance of the <see cref="QWordWatch"/> class
/// </summary>
/// <param name="domain"><see cref="MemoryDomain"/> where you want to track</param>
/// <param name="address">The address you want to track</param>
/// <param name="type">How you you want to display the value See <see cref="WatchDisplayType"/></param>
/// <param name="bigEndian">Specify the endianess. true for big endian</param>
/// <param name="note">A custom note about the <see cref="Watch"/></param>
/// <param name="value">Current value</param>
/// <param name="previous">Previous value</param>
/// <param name="changeCount">How many times value has changed</param>
/// <exception cref="ArgumentException">Occurs when a <see cref="WatchDisplayType"/> is incompatible with <see cref="WatchSize.QWord"/></exception>
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;
}
/// <summary>
/// Get a list of <see cref="WatchDisplayType"/> that can be used for a <see cref="QWordWatch"/>
/// </summary>
/// <returns>An enumeration that contains all valid <see cref="WatchDisplayType"/></returns>
public override IReadOnlyList<WatchDisplayType> AvailableTypes()
=> ValidTypes;
/// <summary>
/// Reset the previous value; set it to the current one
/// </summary>
public override void ResetPrevious()
=> _previous = GetQWord();
/// <summary>
/// Try to sets the value into the <see cref="MemoryDomain"/>
/// at the current <see cref="Watch"/> address
/// </summary>
/// <param name="value">Value to set</param>
/// <returns>True if value successfully sets; otherwise, false</returns>
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;
}
}
/// <summary>
/// Update the Watch (read it from <see cref="MemoryDomain"/>
/// </summary>
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(),
};
}
/// <summary>
/// Get a string representation of difference
/// between current value and the previous one
/// </summary>
public override string Diff
=> $"{unchecked((long) _value - (long) _previous):+#;-#;0}";
/// <summary>
/// Returns true if the Watch is valid, false otherwise
/// </summary>
public override bool IsValid
=> Domain.Size is 0 || Address < (Domain.Size - (sizeof(ulong) - 1));
/// <summary>
/// Get the maximum possible value
/// </summary>
public override ulong MaxValue
=> ulong.MaxValue;
/// <summary>
/// Get the current value
/// </summary>
public override long Value
=> unchecked((long) GetQWord());
/// <summary>
/// Get a string representation of the current value
/// </summary>
public override string ValueString
=> FormatValue(GetQWord());
/// <summary>
/// Get the previous value
/// </summary>
public override ulong Previous
=> _previous;
/// <summary>
/// Get a string representation of the previous value
/// </summary>
public override string PreviousStr
=> FormatValue(_previous);
}
}

View File

@ -10,7 +10,7 @@ namespace BizHawk.Client.Common
{
/// <summary>
/// This class holds a watch i.e. something inside a <see cref="MemoryDomain"/> 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
/// </summary>
[DebuggerDisplay("Note={Notes}, Value={ValueString}")]
@ -117,7 +117,8 @@ namespace BizHawk.Client.Common
/// <summary>
/// Generates a new <see cref="Watch"/> instance
/// Can be either <see cref="ByteWatch"/>, <see cref="WordWatch"/>, <see cref="DWordWatch"/> or <see cref="SeparatorWatch"/>
/// Can be either <see cref="ByteWatch"/>, <see cref="WordWatch"/>, <see cref="DWordWatch"/>, <see cref="QWordWatch"/>,
/// or <see cref="SeparatorWatch"/>
/// </summary>
/// <param name="domain">The <see cref="MemoryDomain"/> where you want to watch</param>
/// <param name="address">The address into the <see cref="MemoryDomain"/></param>
@ -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);
}
/// <summary>
/// Sets the number of changes to 0
/// </summary>
@ -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;
}
}

View File

@ -23,6 +23,12 @@
/// </summary>
DWord = 4,
/// <summary>
/// 8 bytes (64 bits)
/// Use this for <see cref="QWordWatch"/>
/// </summary>
QWord = 8,
/// <summary>
/// Special case used for a separator in ram tools
/// Use this for <see cref="SeparatorWatch"/>