2015-12-01 21:16:28 +00:00
|
|
|
|
using System;
|
|
|
|
|
using System.Collections;
|
2015-11-26 22:05:29 +00:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
using System.Globalization;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Text;
|
2015-12-01 21:16:28 +00:00
|
|
|
|
using System.Threading.Tasks;
|
2015-11-26 22:05:29 +00:00
|
|
|
|
|
|
|
|
|
using BizHawk.Common.NumberExtensions;
|
|
|
|
|
using BizHawk.Common.StringExtensions;
|
|
|
|
|
using BizHawk.Emulation.Common;
|
|
|
|
|
|
2015-12-01 21:16:28 +00:00
|
|
|
|
|
2015-11-26 22:05:29 +00:00
|
|
|
|
namespace BizHawk.Client.Common
|
|
|
|
|
{
|
2015-12-01 21:16:28 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// This class hold a collection <see cref="Watch"/>
|
|
|
|
|
/// Different memory domain can be mixed
|
|
|
|
|
/// </summary>
|
|
|
|
|
public sealed partial class WatchList
|
|
|
|
|
: IList<Watch>
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-01 21:16:28 +00:00
|
|
|
|
#region Fields
|
2015-11-26 22:05:29 +00:00
|
|
|
|
|
|
|
|
|
public const string ADDRESS = "AddressColumn";
|
|
|
|
|
public const string VALUE = "ValueColumn";
|
|
|
|
|
public const string PREV = "PrevColumn";
|
|
|
|
|
public const string CHANGES = "ChangesColumn";
|
|
|
|
|
public const string DIFF = "DiffColumn";
|
|
|
|
|
public const string DOMAIN = "DomainColumn";
|
|
|
|
|
public const string NOTES = "NotesColumn";
|
|
|
|
|
|
2015-12-01 21:16:28 +00:00
|
|
|
|
private static readonly WatchDomainComparer domainComparer = new WatchDomainComparer();
|
|
|
|
|
private static readonly WatchAddressComparer addressComparer = new WatchAddressComparer();
|
|
|
|
|
|
|
|
|
|
private static IMemoryDomains _memoryDomains;
|
|
|
|
|
|
|
|
|
|
private List<Watch> _watchList = new List<Watch>(0);
|
|
|
|
|
private MemoryDomain _domain;
|
|
|
|
|
private string _currentFilename = string.Empty;
|
|
|
|
|
private string _systemid;
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region cTor(s)
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initialize a new instance of <see cref="WatchList"/> that will
|
|
|
|
|
/// contains a set of <see cref="Watch"/>
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="core">All available memomry domains</param>
|
|
|
|
|
/// <param name="domain">Domain you want to watch</param>
|
|
|
|
|
/// <param name="systemid">System identifier (NES, SNES, ...)</param>
|
|
|
|
|
[Obsolete("Use the constructor with two parameters instead")]
|
2015-11-26 22:05:29 +00:00
|
|
|
|
public WatchList(IMemoryDomains core, MemoryDomain domain, string systemid)
|
|
|
|
|
{
|
2015-12-01 21:16:28 +00:00
|
|
|
|
if (_memoryDomains == null)
|
|
|
|
|
{
|
|
|
|
|
_memoryDomains = core;
|
|
|
|
|
}
|
2015-11-26 22:05:29 +00:00
|
|
|
|
_domain = domain;
|
|
|
|
|
_systemid = systemid;
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-01 21:16:28 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Initialize a new instance of <see cref="WatchList"/> that will
|
|
|
|
|
/// contains a set of <see cref="Watch"/>
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="core">All available memomry domains</param>
|
|
|
|
|
/// <param name="domain">Domain you want to watch</param>
|
|
|
|
|
/// <param name="systemid">System identifier (NES, SNES, ...)</param>
|
|
|
|
|
public WatchList(IMemoryDomains core, string systemid)
|
|
|
|
|
{
|
|
|
|
|
if (_memoryDomains == null)
|
|
|
|
|
{
|
|
|
|
|
_memoryDomains = core;
|
|
|
|
|
}
|
|
|
|
|
//TODO: Remove this after tests
|
|
|
|
|
_domain = core.MainMemory;
|
|
|
|
|
_systemid = systemid;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Methods
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
#region ICollection<Watch>
|
2015-11-26 22:05:29 +00:00
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Adds a <see cref="Watch"/> into the current collection
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="watch"><see cref="Watch"/> to add</param>
|
|
|
|
|
public void Add(Watch watch)
|
|
|
|
|
{
|
|
|
|
|
_watchList.Add(watch);
|
|
|
|
|
Changes = true;
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
2015-12-01 21:16:28 +00:00
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Removes all item from the current collection
|
|
|
|
|
/// Clear also the file name
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void Clear()
|
2015-12-01 21:16:28 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
_watchList.Clear();
|
|
|
|
|
Changes = false;
|
|
|
|
|
_currentFilename = string.Empty;
|
2015-12-01 21:16:28 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines if the current <see cref="WatchList"/> contains the
|
|
|
|
|
/// specified <see cref="Watch"/>
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="watch">The object to</param>
|
|
|
|
|
/// <returns></returns>
|
|
|
|
|
public bool Contains(Watch watch)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
return _watchList.Contains(watch);
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Copies the elements of the current <see cref="WatchList"/>
|
|
|
|
|
/// into an <see cref="Array"/> starting at a particular <see cref="Array"/> index
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="array">The one-dimension <see cref="Array"/> that will serve as destination to copy</param>
|
|
|
|
|
/// <param name="arrayIndex">Zero-based index where the copy should starts</param>
|
|
|
|
|
/// <exception cref="ArgumentNullException"></exception>
|
|
|
|
|
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
|
|
|
/// <exception cref="ArgumentException"></exception>
|
|
|
|
|
public void CopyTo(Watch[] array, int arrayIndex)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
_watchList.CopyTo(array, arrayIndex);
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Removes the first of specified <see cref="Watch"/>
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="watch"><see cref="Watch"/> to remove</param>
|
|
|
|
|
/// <returns>True if <see cref="Watch"/> successfully removed; otherwise, false</returns>
|
|
|
|
|
public bool Remove(Watch watch)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
bool result = _watchList.Remove(watch);
|
|
|
|
|
if (result)
|
2015-12-01 21:16:28 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
Changes = true;
|
2015-12-01 21:16:28 +00:00
|
|
|
|
}
|
2015-12-02 21:47:24 +00:00
|
|
|
|
|
|
|
|
|
return result;
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region IList<Watch>
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Determines the zero-base position of the specified <see cref="Watch"/>
|
|
|
|
|
/// into the <see cref="WatchList"/>
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="watch"><see cref="Watch"/> to look for</param>
|
|
|
|
|
/// <returns>Zero-base position if <see cref="Watch"/> has been found; otherwise -1</returns>
|
|
|
|
|
public int IndexOf(Watch watch)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
return _watchList.IndexOf(watch);
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Insert a <see cref="Watch"/> at the specified index
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="index">The zero-base index where the <see cref="Watch"/> should be inserted</param>
|
|
|
|
|
/// <param name="watch"><see cref="Watch"/> to insert</param>
|
|
|
|
|
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
|
|
|
public void Insert(int index, Watch watch)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
_watchList.Insert(index, watch);
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Removes item at the specified index
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="index">Zero-based index of the <see cref="Watch"/> to remove</param>
|
|
|
|
|
/// <exception cref="ArgumentOutOfRangeException"></exception>
|
|
|
|
|
public void RemoveAt(int index)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
_watchList.RemoveAt(index);
|
|
|
|
|
Changes = true;
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
#endregion IList<Watch>
|
2015-11-26 22:05:29 +00:00
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
#region IEnumerable<Watch>
|
2015-11-26 22:05:29 +00:00
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns an enumerator that iterates through the collection
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>An <see cref="IEnumerator{T}"/> for the current collection</returns>
|
2015-11-26 22:05:29 +00:00
|
|
|
|
public IEnumerator<Watch> GetEnumerator()
|
|
|
|
|
{
|
|
|
|
|
return _watchList.GetEnumerator();
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Returns an enumerator that iterates through the collection
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <returns>An <see cref="IEnumerator"/> for the current collection</returns>
|
2015-11-26 22:05:29 +00:00
|
|
|
|
IEnumerator IEnumerable.GetEnumerator()
|
|
|
|
|
{
|
|
|
|
|
return GetEnumerator();
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
#endregion IEnumerable<Watch>
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Add an existing collection of <see cref="Watch"/> into the current one
|
|
|
|
|
/// <see cref="Watch"/> equality will be checked to avoid doubles
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="watches"><see cref="IEnumerable{Watch}"/> of watch to merge</param>
|
|
|
|
|
public void AddRange(IEnumerable<Watch> watches)
|
|
|
|
|
{
|
|
|
|
|
Parallel.ForEach<Watch>(watches, watch =>
|
|
|
|
|
{
|
|
|
|
|
if (!_watchList.Contains(watch))
|
|
|
|
|
{
|
|
|
|
|
_watchList.Add(watch);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
Changes = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Clears change count of all <see cref="Watch"/> in the collection
|
|
|
|
|
/// </summary>
|
|
|
|
|
public void ClearChangeCounts()
|
|
|
|
|
{
|
|
|
|
|
Parallel.ForEach<Watch>(_watchList, watch => watch.ClearChangeCount());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sort the current list based on one of the constant
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="column">Value that specify sorting base</param>
|
|
|
|
|
/// <param name="reverse">Value that define the ordering. Ascending (true) or desceding (false)</param>
|
2015-11-26 22:05:29 +00:00
|
|
|
|
public void OrderWatches(string column, bool reverse)
|
|
|
|
|
{
|
|
|
|
|
switch (column)
|
|
|
|
|
{
|
|
|
|
|
case ADDRESS:
|
|
|
|
|
if (reverse)
|
|
|
|
|
{
|
2015-12-01 21:16:28 +00:00
|
|
|
|
_watchList.Sort(addressComparer);
|
|
|
|
|
_watchList.Reverse();
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-12-01 21:16:28 +00:00
|
|
|
|
_watchList.Sort();
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case VALUE:
|
|
|
|
|
if (reverse)
|
|
|
|
|
{
|
|
|
|
|
_watchList = _watchList
|
2015-11-29 16:13:32 +00:00
|
|
|
|
.OrderByDescending(x => x.Value)
|
|
|
|
|
.ThenBy(x => x.Address)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
.ThenBy(x => x.Size)
|
|
|
|
|
.ThenBy(x => x.Type)
|
|
|
|
|
.ThenBy(x => x.BigEndian)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_watchList = _watchList
|
2015-11-29 16:13:32 +00:00
|
|
|
|
.OrderBy(x => x.Value)
|
|
|
|
|
.ThenBy(x => x.Address)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
.ThenBy(x => x.Size)
|
|
|
|
|
.ThenBy(x => x.Type)
|
|
|
|
|
.ThenBy(x => x.BigEndian)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case PREV: // Note: these only work if all entries are detailed objects!
|
|
|
|
|
if (reverse)
|
|
|
|
|
{
|
|
|
|
|
_watchList = _watchList
|
|
|
|
|
.OrderByDescending(x => x.PreviousStr)
|
2015-12-01 21:16:28 +00:00
|
|
|
|
.ThenBy(x => x.Address)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
.ThenBy(x => x.Size)
|
|
|
|
|
.ThenBy(x => x.Type)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_watchList = _watchList
|
|
|
|
|
.OrderBy(x => x.PreviousStr)
|
2015-12-01 21:16:28 +00:00
|
|
|
|
.ThenBy(x => x.Address)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
.ThenBy(x => x.Size)
|
|
|
|
|
.ThenBy(x => x.Type)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case DIFF:
|
|
|
|
|
if (reverse)
|
|
|
|
|
{
|
|
|
|
|
_watchList = _watchList
|
|
|
|
|
.OrderByDescending(x => x.Diff)
|
2015-12-01 21:16:28 +00:00
|
|
|
|
.ThenBy(x => x.Address)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
.ThenBy(x => x.Size)
|
|
|
|
|
.ThenBy(x => x.Type)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_watchList = _watchList
|
|
|
|
|
.OrderBy(x => x.Diff)
|
2015-12-01 21:16:28 +00:00
|
|
|
|
.ThenBy(x => x.Address)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
.ThenBy(x => x.Size)
|
|
|
|
|
.ThenBy(x => x.Type)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case CHANGES:
|
|
|
|
|
if (reverse)
|
|
|
|
|
{
|
|
|
|
|
_watchList = _watchList
|
|
|
|
|
.OrderByDescending(x => x.ChangeCount)
|
2015-12-01 21:16:28 +00:00
|
|
|
|
.ThenBy(x => x.Address)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
.ThenBy(x => x.Size)
|
|
|
|
|
.ThenBy(x => x.Type)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_watchList = _watchList
|
|
|
|
|
.OrderBy(x => x.ChangeCount)
|
2015-12-01 21:16:28 +00:00
|
|
|
|
.ThenBy(x => x.Address)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
.ThenBy(x => x.Size)
|
|
|
|
|
.ThenBy(x => x.Type)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case DOMAIN:
|
|
|
|
|
if (reverse)
|
|
|
|
|
{
|
2015-12-01 21:16:28 +00:00
|
|
|
|
_watchList.Sort(domainComparer);
|
|
|
|
|
_watchList.Reverse();
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
2015-12-01 21:16:28 +00:00
|
|
|
|
_watchList.Sort(domainComparer);
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
case NOTES:
|
|
|
|
|
if (reverse)
|
|
|
|
|
{
|
|
|
|
|
_watchList = _watchList
|
|
|
|
|
.OrderByDescending(x => x.Notes)
|
2015-12-01 21:16:28 +00:00
|
|
|
|
.ThenBy(x => x.Address)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
.ThenBy(x => x.Size)
|
|
|
|
|
.ThenBy(x => x.Type)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
_watchList = _watchList
|
|
|
|
|
.OrderBy(x => x.Notes)
|
2015-12-01 21:16:28 +00:00
|
|
|
|
.ThenBy(x => x.Address)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
.ThenBy(x => x.Size)
|
|
|
|
|
.ThenBy(x => x.Type)
|
|
|
|
|
.ToList();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Sets WatchList's domain list to a new one
|
|
|
|
|
/// <see cref="Watch"/> domain will also be refreshed
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="core">New domains</param>
|
|
|
|
|
public void RefreshDomains(IMemoryDomains core)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
_memoryDomains = core;
|
|
|
|
|
Parallel.ForEach<Watch>(_watchList, watch =>
|
|
|
|
|
{
|
|
|
|
|
watch.Domain = core[watch.Domain.Name];
|
|
|
|
|
watch.ResetPrevious();
|
|
|
|
|
watch.Update();
|
|
|
|
|
watch.ClearChangeCount();
|
|
|
|
|
}
|
|
|
|
|
);
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Updates all <see cref="Watch"/> ine the current collection
|
|
|
|
|
/// </summary>
|
2015-11-26 22:05:29 +00:00
|
|
|
|
public void UpdateValues()
|
|
|
|
|
{
|
2015-12-01 21:16:28 +00:00
|
|
|
|
Parallel.ForEach<Watch>(_watchList, watch =>
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
|
|
|
|
watch.Update();
|
2015-12-01 21:16:28 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
2015-11-26 22:05:29 +00:00
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
public string AddressFormatStr // TODO: this is probably compensating for not using the ToHex string extension
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
if (_domain != null)
|
|
|
|
|
{
|
|
|
|
|
return "{0:X" + (_domain.Size - 1).NumHexDigits() + "}";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return string.Empty;
|
|
|
|
|
}
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
#region Propeties
|
|
|
|
|
|
|
|
|
|
#region ICollection<Watch>
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the number of elements contained in this <see cref="WatchList"/>
|
|
|
|
|
/// </summary>
|
|
|
|
|
public int Count
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return _watchList.Count;
|
|
|
|
|
}
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// <see cref="WatchList"/> is alsways read-write
|
|
|
|
|
/// so this value will be always false
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool IsReadOnly
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
get
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
return false;
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
#endregion ICollection<Watch>
|
|
|
|
|
|
|
|
|
|
#region IList<Watch>
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets or sets element at the specified index
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="index">The zero based index of the element you want to get or set</param>
|
|
|
|
|
/// <returns><see cref="Watch"/> at the specified index</returns>
|
|
|
|
|
public Watch this[int index]
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return _watchList[index];
|
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
_watchList[index] = value;
|
|
|
|
|
}
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
#endregion IList<Watch>
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets a value indicating if collection has changed or not
|
|
|
|
|
/// </summary>
|
|
|
|
|
public bool Changes { get; set; }
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets or sets current <see cref="WatchList"/>'s filename
|
|
|
|
|
/// </summary>
|
|
|
|
|
public string CurrentFileName
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
get
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
return _currentFilename;
|
|
|
|
|
}
|
|
|
|
|
set
|
|
|
|
|
{
|
|
|
|
|
_currentFilename = value;
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Gets the number of <see cref="Watch"/> that are not <see cref="SeparatorWatch"/>
|
|
|
|
|
/// </summary>
|
|
|
|
|
public int WatchCount
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return _watchList.Count<Watch>(watch => !watch.IsSeparator);
|
|
|
|
|
}
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
[Obsolete("Use the method with single parameter instead")]
|
|
|
|
|
public void RefreshDomains(IMemoryDomains core, MemoryDomain domain)
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
_memoryDomains = core;
|
|
|
|
|
_domain = domain;
|
|
|
|
|
|
|
|
|
|
_watchList.ForEach(w =>
|
|
|
|
|
{
|
|
|
|
|
if (w.Domain != null)
|
|
|
|
|
{
|
|
|
|
|
w.Domain = _memoryDomains[w.Domain.Name];
|
|
|
|
|
}
|
|
|
|
|
});
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
[Obsolete("Use count property instead")]
|
|
|
|
|
public int ItemCount
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
get
|
|
|
|
|
{
|
|
|
|
|
return Count;
|
|
|
|
|
}
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
2015-12-02 21:47:24 +00:00
|
|
|
|
[Obsolete("Use domain from individual watch instead")]
|
|
|
|
|
public MemoryDomain Domain
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
2015-12-02 21:47:24 +00:00
|
|
|
|
get { return _domain; }
|
|
|
|
|
set { _domain = value; }
|
2015-11-26 22:05:29 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#region File handling logic - probably needs to be its own class
|
|
|
|
|
|
|
|
|
|
public bool Load(string path, bool append)
|
|
|
|
|
{
|
|
|
|
|
var result = LoadFile(path, append);
|
|
|
|
|
|
|
|
|
|
if (result)
|
|
|
|
|
{
|
|
|
|
|
if (append)
|
|
|
|
|
{
|
|
|
|
|
Changes = true;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
CurrentFileName = path;
|
|
|
|
|
Changes = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Reload()
|
|
|
|
|
{
|
|
|
|
|
if (!string.IsNullOrWhiteSpace(CurrentFileName))
|
|
|
|
|
{
|
|
|
|
|
LoadFile(CurrentFileName, append: false);
|
|
|
|
|
Changes = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool Save()
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrWhiteSpace(CurrentFileName))
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
using (var sw = new StreamWriter(CurrentFileName))
|
|
|
|
|
{
|
|
|
|
|
var sb = new StringBuilder();
|
|
|
|
|
sb
|
|
|
|
|
.Append("Domain ").AppendLine(_domain.Name)
|
|
|
|
|
.Append("SystemID ").AppendLine(_systemid);
|
|
|
|
|
|
|
|
|
|
foreach (var watch in _watchList)
|
|
|
|
|
{
|
|
|
|
|
sb.AppendLine(watch.ToString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sw.WriteLine(sb.ToString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Changes = false;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool SaveAs(FileInfo file)
|
|
|
|
|
{
|
|
|
|
|
if (file != null)
|
|
|
|
|
{
|
|
|
|
|
CurrentFileName = file.FullName;
|
|
|
|
|
return Save();
|
|
|
|
|
}
|
2015-12-01 21:16:28 +00:00
|
|
|
|
|
2015-11-26 22:05:29 +00:00
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private bool LoadFile(string path, bool append)
|
|
|
|
|
{
|
|
|
|
|
var domain = string.Empty;
|
|
|
|
|
var file = new FileInfo(path);
|
|
|
|
|
if (file.Exists == false)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var isBizHawkWatch = true; // Hack to support .wch files from other emulators
|
|
|
|
|
var isOldBizHawkWatch = false;
|
|
|
|
|
using (var sr = file.OpenText())
|
|
|
|
|
{
|
|
|
|
|
string line;
|
|
|
|
|
|
|
|
|
|
if (!append)
|
|
|
|
|
{
|
|
|
|
|
Clear();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while ((line = sr.ReadLine()) != null)
|
|
|
|
|
{
|
|
|
|
|
// .wch files from other emulators start with a number representing the number of watch, that line can be discarded here
|
|
|
|
|
// Any properly formatted line couldn't possibly be this short anyway, this also takes care of any garbage lines that might be in a file
|
|
|
|
|
if (line.Length < 5)
|
|
|
|
|
{
|
|
|
|
|
isBizHawkWatch = false;
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (line.Length >= 6 && line.Substring(0, 6) == "Domain")
|
|
|
|
|
{
|
|
|
|
|
domain = line.Substring(7, line.Length - 7);
|
|
|
|
|
isBizHawkWatch = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (line.Length >= 8 && line.Substring(0, 8) == "SystemID")
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var numColumns = line.HowMany('\t');
|
|
|
|
|
int startIndex;
|
|
|
|
|
if (numColumns == 5)
|
|
|
|
|
{
|
|
|
|
|
// If 5, then this is a post 1.0.5 .wch file
|
|
|
|
|
if (isBizHawkWatch)
|
|
|
|
|
{
|
|
|
|
|
// Do nothing here
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
startIndex = line.IndexOf('\t') + 1;
|
|
|
|
|
line = line.Substring(startIndex, line.Length - startIndex); // 5 digit value representing the watch position number
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (numColumns == 4)
|
|
|
|
|
{
|
|
|
|
|
isOldBizHawkWatch = true; // This supports the legacy .wch format from 1.0.5 and earlier
|
|
|
|
|
}
|
2015-12-01 21:16:28 +00:00
|
|
|
|
else
|
2015-11-26 22:05:29 +00:00
|
|
|
|
{
|
|
|
|
|
continue; // If not 4, something is wrong with this line, ignore it
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Temporary, rename if kept
|
|
|
|
|
int addr;
|
|
|
|
|
var memDomain = _memoryDomains.MainMemory;
|
|
|
|
|
|
|
|
|
|
var temp = line.Substring(0, line.IndexOf('\t'));
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
addr = int.Parse(temp, NumberStyles.HexNumber);
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startIndex = line.IndexOf('\t') + 1;
|
|
|
|
|
line = line.Substring(startIndex, line.Length - startIndex); // Type
|
|
|
|
|
var size = Watch.SizeFromChar(line[0]);
|
|
|
|
|
|
|
|
|
|
startIndex = line.IndexOf('\t') + 1;
|
|
|
|
|
line = line.Substring(startIndex, line.Length - startIndex); // Signed
|
|
|
|
|
var type = Watch.DisplayTypeFromChar(line[0]);
|
|
|
|
|
|
|
|
|
|
startIndex = line.IndexOf('\t') + 1;
|
|
|
|
|
line = line.Substring(startIndex, line.Length - startIndex); // Endian
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
startIndex = short.Parse(line[0].ToString());
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var bigEndian = startIndex != 0;
|
|
|
|
|
|
|
|
|
|
if (isBizHawkWatch && !isOldBizHawkWatch)
|
|
|
|
|
{
|
|
|
|
|
startIndex = line.IndexOf('\t') + 1;
|
|
|
|
|
line = line.Substring(startIndex, line.Length - startIndex); // Domain
|
|
|
|
|
temp = line.Substring(0, line.IndexOf('\t'));
|
|
|
|
|
memDomain = _memoryDomains[temp] ?? _memoryDomains.MainMemory;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
startIndex = line.IndexOf('\t') + 1;
|
|
|
|
|
var notes = line.Substring(startIndex, line.Length - startIndex);
|
|
|
|
|
|
|
|
|
|
_watchList.Add(
|
|
|
|
|
Watch.GenerateWatch(
|
|
|
|
|
memDomain,
|
|
|
|
|
addr,
|
|
|
|
|
size,
|
2015-12-01 21:16:28 +00:00
|
|
|
|
type,
|
2015-11-28 21:47:16 +00:00
|
|
|
|
bigEndian,
|
2015-12-01 21:16:28 +00:00
|
|
|
|
notes));
|
2015-11-26 22:05:29 +00:00
|
|
|
|
_domain = _memoryDomains[domain];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Domain = _memoryDomains[domain] ?? _memoryDomains.MainMemory;
|
|
|
|
|
_currentFilename = path;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!append)
|
|
|
|
|
{
|
|
|
|
|
Changes = false;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
Changes = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
#endregion
|
|
|
|
|
}
|
|
|
|
|
}
|