using System; using System.Collections.Generic; using System.Globalization; using System.Linq; using System.Diagnostics; using BizHawk.Common.NumberExtensions; using BizHawk.Emulation.Common; 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). /// This is an abstract class /// [DebuggerDisplay("Note={Notes}, Value={ValueString}")] public abstract partial class Watch : IEquatable, IEquatable, IComparable { #region Fields protected long _address; protected MemoryDomain _domain; protected DisplayType _type; protected WatchSize _size; protected bool _bigEndian; protected string _notes; protected int _changecount = 0; #endregion #region cTor(s) /// /// Initialize a new instance of /// /// where you want to track /// The address you want to track /// A (byte, word, double word) /// How you you want to display the value See /// Specify the endianess. true for big endian /// A custom note about the /// Occurs when a is incompatible with the protected Watch(MemoryDomain domain, long address, WatchSize size, DisplayType type, bool bigEndian, string note) { if (IsDiplayTypeAvailable(type)) { this._domain = domain; this._address = address; this._size = size; this._type = type; this._bigEndian = bigEndian; this._notes = note; return; } else { throw new ArgumentException(string.Format("DisplayType {0} is invalid for this type of Watch", type.ToString()), "type"); } } #endregion #region Methods #region Static /// /// Generate sa from a given string /// String is tab separate /// /// Entire string, tab seperated for each value Order is: /// /// /// 0x00 /// Address in hexadecimal /// /// /// b,w or d /// The , byte, word or double word /// s, u, h, b, 1, 2, 3, f /// The signed, unsigned,etc... /// /// /// 0 or 1 /// Big endian or not /// /// /// RDRAM,ROM,... /// The /// /// /// Plain text /// Notes /// /// /// /// 's memory domain /// A brand new public static Watch FromString(string line, IMemoryDomains domains) { string[] parts = line.Split(new char[] { '\t' }, 6); if (parts.Length < 6) { if (parts.Length >= 3 && parts[2] == "_") { return SeparatorWatch.Instance; } return null; } long address; if (long.TryParse(parts[0], NumberStyles.HexNumber, CultureInfo.CurrentCulture, out address)) { WatchSize size = Watch.SizeFromChar(parts[1][0]); DisplayType type = Watch.DisplayTypeFromChar(parts[2][0]); bool bigEndian = parts[3] == "0" ? false : true; MemoryDomain domain = domains[parts[4]]; string notes = parts[5].Trim(new char[] { '\r', '\n' }); return Watch.GenerateWatch( domain, address, size, type, bigEndian, notes ); } else { return null; } } /// /// Generates a new instance /// Can be either , , or /// /// The where you want to watch /// The address into the /// The size /// How the watch will be displayed /// Endianess (true for big endian) /// A custom note about the /// The current watch value /// 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, DisplayType type, bool bigEndian, string note, long value, long prev, int changeCount) { switch (size) { default: case WatchSize.Separator: return SeparatorWatch.Instance; case WatchSize.Byte: return new ByteWatch(domain, address, type, bigEndian, note, (byte)value, (byte)prev, changeCount); case WatchSize.Word: return new WordWatch(domain, address, type, bigEndian, note, (ushort)value, (ushort)prev, changeCount); case WatchSize.DWord: return new DWordWatch(domain, address, type, bigEndian, note, (uint)value, (uint)prev, changeCount); } } /// /// Generates a new instance /// Can be either , , or /// /// The where you want to watch /// The address into the /// The size /// How the watch will be displayed /// Endianess (true for big endian) /// A customp note about your watch /// New instance. True type is depending of size parameter public static Watch GenerateWatch(MemoryDomain domain, long address, WatchSize size, DisplayType type, bool bigEndian, string note) { return GenerateWatch(domain, address, size, type, bigEndian, note, 0, 0, 0); } /// /// Generates a new instance /// Can be either , , or /// /// The where you want to watch /// The address into the /// The size /// How the watch will be displayed /// Endianess (true for big endian) /// New instance. True type is depending of size parameter public static Watch GenerateWatch(MemoryDomain domain, long address, WatchSize size, DisplayType type, bool bigEndian) { return GenerateWatch(domain, address, size, type, bigEndian, string.Empty, 0, 0, 0); } #region Operators /// /// Equality operator between two /// /// First watch /// Second watch /// True if both watch are equals; otherwise, false public static bool operator ==(Watch a, Watch b) { if (object.ReferenceEquals(a, null) || object.ReferenceEquals(b, null)) { return false; } else if (object.ReferenceEquals(a, b)) { return true; } else { return a.Equals(b); } } /// /// Equality operator between a and a /// /// The watch /// The cheat /// True if they are equals; otherwise, false public static bool operator ==(Watch a, Cheat b) { if (object.ReferenceEquals(a, null) || object.ReferenceEquals(b, null)) { return false; } else if (object.ReferenceEquals(a, b)) { return true; } else { return a.Equals(b); } } /// /// Inequality operator between two /// /// First watch /// Second watch /// True if both watch are different; otherwise, false public static bool operator !=(Watch a, Watch b) { return !(a == b); } /// /// Inequality operator between a and a /// /// The watch /// The cheat /// True if they are different; otherwise, false public static bool operator !=(Watch a, Cheat b) { return !(a == b); } /// /// Compare two together /// /// First /// Second /// True if first is lesser than b; otherwise, false /// Occurs when you try to compare two throughout different public static bool operator <(Watch a, Watch b) { return a.CompareTo(b) < 0; } /// /// Compare two together /// /// First /// Second /// True if first is greater than b; otherwise, false /// Occurs when you try to compare two throughout different public static bool operator >(Watch a, Watch b) { return a.CompareTo(b) > 0; } /// /// Compare two together /// /// First /// Second /// True if first is lesser or equals to b; otherwise, false /// Occurs when you try to compare two throughout different public static bool operator <=(Watch a, Watch b) { return a.CompareTo(b) <= 0; } /// /// Compare two together /// /// First /// Second /// True if first is greater or equals to b; otherwise, false /// Occurs when you try to compare two throughout different public static bool operator >=(Watch a, Watch b) { return a.CompareTo(b) >= 0; } #endregion Operators #endregion Static #region Abstracts /// /// Gets a list a that can be used for this /// /// An enumartion that contains all valid public abstract IEnumerable AvailableTypes(); /// /// Resets the previous value; set it to the current one /// public abstract void ResetPrevious(); /// /// Updates the Watch (read it from /// public abstract void Update(); #endregion Abstracts #region Protected protected byte GetByte(bool bypassFreeze = false) { if (!bypassFreeze && Global.CheatList.IsActive(_domain, _address)) { //LIAR logic return Global.CheatList.GetByteValue(_domain, _address) ?? 0; } else { if (_domain.Size == 0) { return _domain.PeekByte(_address); } else { return _domain.PeekByte(_address % _domain.Size); } } } protected ushort GetWord(bool bypassFreeze = false) { if (!bypassFreeze && Global.CheatList.IsActive(_domain, _address)) { //LIAR logic return (ushort)(Global.CheatList.GetCheatValue(_domain, _address, WatchSize.Word) ?? 0); } else { if (_domain.Size == 0) { return _domain.PeekWord(_address, _bigEndian); } else { return _domain.PeekWord(_address % _domain.Size, _bigEndian); // TODO: % size stil lisn't correct since it could be the last byte of the domain } } } protected uint GetDWord(bool bypassFreeze = false) { if (!bypassFreeze && Global.CheatList.IsActive(_domain, _address)) { //LIAR logic return (uint)(Global.CheatList.GetCheatValue(_domain, _address, WatchSize.DWord) ?? 0); } else { if (_domain.Size == 0) { return _domain.PeekDWord(_address, _bigEndian); // TODO: % size stil lisn't correct since it could be the last byte of the domain } else { return _domain.PeekDWord(_address % _domain.Size, _bigEndian); // TODO: % size stil lisn't correct since it could be the last byte of the domain } } } protected void PokeByte(byte val) { if (_domain.Size == 0) _domain.PokeByte(_address, val); else _domain.PokeByte(_address % _domain.Size, val); } protected void PokeWord(ushort val) { if (_domain.Size == 0) _domain.PokeWord(_address, val, _bigEndian); // TODO: % size stil lisn't correct since it could be the last byte of the domain else _domain.PokeWord(_address % _domain.Size, val, _bigEndian); // TODO: % size stil lisn't correct since it could be the last byte of the domain } protected void PokeDWord(uint val) { if (_domain.Size == 0) _domain.PokeDWord(_address, val, _bigEndian); // TODO: % size stil lisn't correct since it could be the last byte of the domain else _domain.PokeDWord(_address % _domain.Size, val, _bigEndian); // TODO: % size stil lisn't correct since it could be the last byte of the domain } #endregion Protected /// /// Sets the number of changes to 0 /// public void ClearChangeCount() { _changecount = 0; } #region IEquatable /// /// Determines if this is equals to another /// /// The to compare /// True if both object are equals; otherwise, false public bool Equals(Watch other) { if (object.ReferenceEquals(other, null)) { return false; } else { return this._domain == other._domain && this._address == other._address && this._size == other._size; } } #endregion IEquatable #region IEquatable /// /// Determines if this is equals to an instance of /// /// The to compare /// True if both object are equals; otherwise, false public bool Equals(Cheat other) { return !object.ReferenceEquals(other, null) && this._domain == other.Domain && this._address == other.Address && this._size == other.Size; } #endregion IEquatable #region IComparable /// /// Compares two together and determine wich one comes first. /// First we look the address and then the size /// /// The other to compare to /// 0 if they are equals, 1 if the other is greater, -1 if the other is lesser /// Occurs when you try to compare two throughout different public int CompareTo(Watch other) { if (this._domain != other._domain) { throw new InvalidOperationException("Watch cannot be compared through different domain"); } if (this.Equals(other)) { return 0; } else if (object.ReferenceEquals(other, null)) { return 1; } else if (_address.Equals(other._address)) { return ((int)_size).CompareTo((int)other._size); } else { return _address.CompareTo(other._address); } } #endregion IComparable /// /// Determines if this object is Equals to another /// /// The object to compare /// True if both object are equals; otherwise, false public override bool Equals(object obj) { if (obj is Watch) { return Equals((Watch)obj); } else if (obj is Cheat) { return Equals((Cheat)obj); } else { return base.Equals(obj); } } /// /// Hash the current watch and gets a unique value /// /// that can serves as a unique representation of current Watch public override int GetHashCode() { return this.Domain.GetHashCode() + (int)(this.Address); } /// /// Determines if the specified can be /// used for the current /// /// you want to check /// public bool IsDiplayTypeAvailable(DisplayType type) { return AvailableTypes().Where(d => d == type).Any(); } /// /// Transforms the current instance into a string /// /// A representation of the current public override string ToString() { return string.Format("{0}\t{1}\t{2}\t{3}\t{4}\t{5}" , Domain == null && Address == 0 ? "0" : Address.ToHexString((Domain.Size - 1).NumHexDigits()) , SizeAsChar , TypeAsChar , Convert.ToInt32(BigEndian) , DomainName , Notes.Trim('\r', '\n') ); } /// /// Transform the current instance into a displayable (short representation) string /// It's used by the "Display on screen" option in the RamWatch window /// /// A well formatted string representation public virtual string ToDisplayString() { return string.Format("{0}: {1}", Notes, ValueString); } #endregion #region Properties #region Abstracts /// /// Get a string representation of difference /// between current value and the previous one /// public abstract string Diff { get; } /// /// Get the maximum possible value /// public abstract uint MaxValue { get; } /// /// Get the current value /// public abstract int Value { get; } /// /// Gets the current value /// but with stuff I don't understand /// /// zero 15-nov-2015 - bypass LIAR LOGIC, see fdc9ea2aa922876d20ba897fb76909bf75fa6c92 https://github.com/TASVideos/BizHawk/issues/326 public abstract int ValueNoFreeze { get; } /// /// Get a string representation of the current value /// public abstract string ValueString { get; } /// /// Try to sets the value into the /// at the current address /// /// Value to set /// True if value successfully sets; othewise, false public abstract bool Poke(string value); /// /// Get the previous value /// public abstract int Previous { get; } /// /// Get a string representation of the previous value /// public abstract string PreviousStr { get; } #endregion Abstracts /// /// Gets the address in the /// public long Address { get { return _address; } } /// /// Gets the format tha should be used by string.Format() /// private string AddressFormatStr { get { if (_domain != null) { return "X" + (_domain.Size - 1).NumHexDigits(); } return string.Empty; } } /// /// Gets the address in the formatted as string /// public string AddressString { get { return _address.ToString(AddressFormatStr); } } /// /// Gets or sets the endianess of current /// True for big endian, flase for little endian /// public bool BigEndian { get { return _bigEndian; } set { _bigEndian = value; } } /// /// Gets the number of time tha value of current has changed /// public int ChangeCount { get { return _changecount; } } /// /// Gets or set the way current is displayed /// /// Occurs when a is incompatible with the public DisplayType Type { get { return _type; } set { if (IsDiplayTypeAvailable(value)) { _type = value; } else { throw new ArgumentException(string.Format("DisplayType {0} is invalid for this type of Watch", value.ToString())); } } } /// /// Gets or sets current /// public MemoryDomain Domain { get { return _domain; } internal set { if (_domain.Name == value.Name) { _domain = value; } else { throw new InvalidOperationException("You cannot set diffrent domain to a watch on the fly"); } } } /// /// Gets the domain name of the current /// It's the same of doing myWatch.Domain.Name /// public string DomainName { get { if (_domain != null) { return _domain.Name; } else { return string.Empty; } } } /// /// Gets a value that defined if the current address is /// well in the range of current /// public bool IsOutOfRange { get { return !IsSeparator && (_domain.Size != 0 && _address >= _domain.Size); } } /// /// Gets a value that defined if the current is actually a /// public bool IsSeparator { get { return this is SeparatorWatch; } } /// /// Gets or sets notes for current /// public string Notes { get { return _notes; } set { _notes = value; } } /// /// Gets the current size of the watch /// public WatchSize Size { get { return _size; } } #endregion //TODO: Replace all the following stuff by implementing ISerializable public static string DisplayTypeToString(DisplayType type) { switch (type) { default: return type.ToString(); case DisplayType.FixedPoint_12_4: return "Fixed Point 12.4"; case DisplayType.FixedPoint_20_12: return "Fixed Point 20.12"; case DisplayType.FixedPoint_16_16: return "Fixed Point 16.16"; } } public static DisplayType StringToDisplayType(string name) { switch (name) { default: return (DisplayType)Enum.Parse(typeof(DisplayType), name); case "Fixed Point 12.4": return DisplayType.FixedPoint_12_4; case "Fixed Point 20.12": return DisplayType.FixedPoint_20_12; case "Fixed Point 16.16": return DisplayType.FixedPoint_16_16; } } public char SizeAsChar { get { switch (Size) { default: case WatchSize.Separator: return 'S'; case WatchSize.Byte: return 'b'; case WatchSize.Word: return 'w'; case WatchSize.DWord: return 'd'; } } } public static WatchSize SizeFromChar(char c) { switch (c) { default: case 'S': return WatchSize.Separator; case 'b': return WatchSize.Byte; case 'w': return WatchSize.Word; case 'd': return WatchSize.DWord; } } public char TypeAsChar { get { switch (Type) { default: case DisplayType.Separator: return '_'; case DisplayType.Unsigned: return 'u'; case DisplayType.Signed: return 's'; case DisplayType.Hex: return 'h'; case DisplayType.Binary: return 'b'; case DisplayType.FixedPoint_12_4: return '1'; case DisplayType.FixedPoint_20_12: return '2'; case DisplayType.FixedPoint_16_16: return '3'; case DisplayType.Float: return 'f'; } } } public static DisplayType DisplayTypeFromChar(char c) { switch (c) { default: case '_': return DisplayType.Separator; case 'u': return DisplayType.Unsigned; case 's': return DisplayType.Signed; case 'h': return DisplayType.Hex; case 'b': return DisplayType.Binary; case '1': return DisplayType.FixedPoint_12_4; case '2': return DisplayType.FixedPoint_20_12; case '3': return DisplayType.FixedPoint_16_16; case 'f': return DisplayType.Float; } } } }