Add 64-bit-wide watches
This commit is contained in:
parent
d34f8a3fa7
commit
17bfab9a2d
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"/>
|
||||
|
|
Loading…
Reference in New Issue