647 lines
14 KiB
C#
647 lines
14 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Text;
|
|
|
|
using BizHawk.Emulation.Common;
|
|
|
|
namespace BizHawk.Client.Common
|
|
{
|
|
public class CheatCollection : ICollection<Cheat>
|
|
{
|
|
private List<Cheat> _cheatList = new List<Cheat>();
|
|
private string _currentFileName = string.Empty;
|
|
private string _defaultFileName = string.Empty;
|
|
private bool _changes;
|
|
|
|
public delegate void CheatListEventHandler(object sender, CheatListEventArgs e);
|
|
public event CheatListEventHandler Changed;
|
|
|
|
public int Count
|
|
{
|
|
get { return _cheatList.Count; }
|
|
}
|
|
|
|
public int CheatCount
|
|
{
|
|
get { return _cheatList.Count(x => !x.IsSeparator); }
|
|
}
|
|
|
|
public int ActiveCount
|
|
{
|
|
get { return _cheatList.Count(x => x.Enabled); }
|
|
}
|
|
|
|
public bool Changes
|
|
{
|
|
get
|
|
{
|
|
return _changes;
|
|
}
|
|
|
|
set
|
|
{
|
|
_changes = value;
|
|
if (value)
|
|
{
|
|
CheatChanged(Cheat.Separator); // Pass a dummy, no cheat invoked this change
|
|
}
|
|
}
|
|
}
|
|
|
|
public string CurrentFileName
|
|
{
|
|
get { return _currentFileName; }
|
|
}
|
|
|
|
public bool IsReadOnly { get { return false; } }
|
|
|
|
public Cheat this[int index]
|
|
{
|
|
get { return _cheatList[index]; }
|
|
}
|
|
|
|
public Cheat this[MemoryDomain domain, int address]
|
|
{
|
|
get
|
|
{
|
|
return _cheatList.FirstOrDefault(cheat => cheat.Domain == domain && cheat.Address == address);
|
|
}
|
|
}
|
|
|
|
public IEnumerator<Cheat> GetEnumerator()
|
|
{
|
|
return _cheatList.GetEnumerator();
|
|
}
|
|
|
|
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
|
|
{
|
|
return GetEnumerator();
|
|
}
|
|
|
|
public void Pulse()
|
|
{
|
|
_cheatList.ForEach(cheat => cheat.Pulse());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Looks for a .cht file that matches the ROM loaded based on the default filename for a given ROM
|
|
/// </summary>
|
|
public bool AttemptToLoadCheatFile()
|
|
{
|
|
var file = new FileInfo(_defaultFileName);
|
|
|
|
if (file.Exists)
|
|
{
|
|
return Load(file.FullName, false);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void NewList(string defaultFileName, bool autosave = false)
|
|
{
|
|
_defaultFileName = defaultFileName;
|
|
|
|
if (_cheatList.Any() && _changes && autosave)
|
|
{
|
|
if (string.IsNullOrEmpty(_currentFileName))
|
|
{
|
|
_currentFileName = _defaultFileName;
|
|
}
|
|
|
|
Save();
|
|
}
|
|
|
|
|
|
_cheatList.Clear();
|
|
_currentFileName = string.Empty;
|
|
Changes = false;
|
|
}
|
|
|
|
public void Add(Cheat cheat)
|
|
{
|
|
if (cheat.IsSeparator)
|
|
{
|
|
_cheatList.Add(cheat);
|
|
}
|
|
else
|
|
{
|
|
cheat.Changed += CheatChanged;
|
|
if (Contains(cheat))
|
|
{
|
|
_cheatList.Remove(Global.CheatList.FirstOrDefault(x => x.Domain == cheat.Domain && x.Address == cheat.Address));
|
|
}
|
|
|
|
_cheatList.Add(cheat);
|
|
}
|
|
|
|
Changes = true;
|
|
}
|
|
|
|
public void AddRange(IEnumerable<Cheat> cheats)
|
|
{
|
|
_cheatList.AddRange(
|
|
cheats.Where(c => !_cheatList.Contains(c)));
|
|
Changes = true;
|
|
}
|
|
|
|
public void Insert(int index, Cheat c)
|
|
{
|
|
c.Changed += CheatChanged;
|
|
if (_cheatList.Any(x => x.Domain == c.Domain && x.Address == c.Address))
|
|
{
|
|
_cheatList.FirstOrDefault(x => x.Domain == c.Domain && x.Address == c.Address).Enable();
|
|
}
|
|
else
|
|
{
|
|
_cheatList.Insert(index, c);
|
|
}
|
|
|
|
Changes = true;
|
|
}
|
|
|
|
public bool Remove(Cheat c)
|
|
{
|
|
var result = _cheatList.Remove(c);
|
|
if (result)
|
|
{
|
|
Changes = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool Remove(Watch watch)
|
|
{
|
|
var cheat = _cheatList.FirstOrDefault(c => c == watch);
|
|
if (cheat != (Cheat)null)
|
|
{
|
|
_cheatList.Remove(cheat);
|
|
Changes = true;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool Contains(Cheat cheat)
|
|
{
|
|
return _cheatList.Any(c => c == cheat);
|
|
}
|
|
|
|
public void CopyTo(Cheat[] array, int arrayIndex)
|
|
{
|
|
_cheatList.CopyTo(array, arrayIndex);
|
|
}
|
|
|
|
public void RemoveRange(IEnumerable<Cheat> cheats)
|
|
{
|
|
foreach (var cheat in cheats.ToList())
|
|
{
|
|
_cheatList.Remove(cheat);
|
|
}
|
|
|
|
Changes = true;
|
|
}
|
|
|
|
public void RemoveRange(IEnumerable<Watch> watches)
|
|
{
|
|
var removeList = _cheatList.Where(cheat => watches.Any(w => w == cheat)).ToList();
|
|
foreach (var cheat in removeList)
|
|
{
|
|
_cheatList.Remove(cheat);
|
|
}
|
|
|
|
Changes = true;
|
|
}
|
|
|
|
public void RemoveAll()
|
|
{
|
|
_cheatList.Clear();
|
|
Changes = true;
|
|
}
|
|
|
|
public void Clear()
|
|
{
|
|
_cheatList.Clear();
|
|
Changes = true;
|
|
}
|
|
|
|
public void DisableAll()
|
|
{
|
|
_cheatList.ForEach(c => c.Disable(false));
|
|
Changes = true;
|
|
}
|
|
|
|
public void EnableAll()
|
|
{
|
|
_cheatList.ForEach(c => c.Enable(false));
|
|
Changes = true;
|
|
}
|
|
|
|
public bool IsActive(MemoryDomain domain, int address)
|
|
{
|
|
return _cheatList.Any(cheat =>
|
|
!cheat.IsSeparator &&
|
|
cheat.Enabled &&
|
|
cheat.Domain == domain
|
|
&& cheat.Contains(address));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns the value of a given byte in a cheat, If the cheat is a single byte this will be the same indexing the cheat,
|
|
/// But if the cheat is multi-byte, this will return just the cheat value for that specific byte
|
|
/// </summary>
|
|
/// <returns>Returns null if address is not a part of a cheat, else returns the value of that specific byte only</returns>
|
|
public byte? GetByteValue(MemoryDomain domain, int addr)
|
|
{
|
|
var activeCheat = _cheatList.FirstOrDefault(cheat => cheat.Contains(addr));
|
|
if (activeCheat == (Cheat)null)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
return activeCheat.GetByteVal(addr);
|
|
}
|
|
|
|
public void SaveOnClose()
|
|
{
|
|
if (Global.Config.CheatsAutoSaveOnClose)
|
|
{
|
|
if (Changes && _cheatList.Any())
|
|
{
|
|
if (string.IsNullOrWhiteSpace(_currentFileName))
|
|
{
|
|
_currentFileName = _defaultFileName;
|
|
}
|
|
|
|
SaveFile(_currentFileName);
|
|
}
|
|
else if (!_cheatList.Any() && !string.IsNullOrWhiteSpace(_currentFileName))
|
|
{
|
|
new FileInfo(_currentFileName).Delete();
|
|
}
|
|
}
|
|
}
|
|
|
|
public bool Save()
|
|
{
|
|
if (string.IsNullOrWhiteSpace(_currentFileName))
|
|
{
|
|
_currentFileName = _defaultFileName;
|
|
}
|
|
|
|
return SaveFile(_currentFileName);
|
|
}
|
|
|
|
public bool SaveFile(string path)
|
|
{
|
|
try
|
|
{
|
|
var file = new FileInfo(path);
|
|
if (file.Directory != null && !file.Directory.Exists)
|
|
{
|
|
file.Directory.Create();
|
|
}
|
|
|
|
using (var sw = new StreamWriter(path))
|
|
{
|
|
var sb = new StringBuilder();
|
|
|
|
foreach (var cheat in _cheatList)
|
|
{
|
|
if (cheat.IsSeparator)
|
|
{
|
|
sb.AppendLine("----");
|
|
}
|
|
else
|
|
{
|
|
// Set to hex for saving
|
|
cheat.SetType(Watch.DisplayType.Hex);
|
|
|
|
sb
|
|
.Append(cheat.AddressStr).Append('\t')
|
|
.Append(cheat.ValueStr).Append('\t')
|
|
.Append(cheat.Compare.HasValue ? cheat.Compare.Value.ToString() : "N").Append('\t')
|
|
.Append(cheat.Domain != null ? cheat.Domain.Name : string.Empty).Append('\t')
|
|
.Append(cheat.Enabled ? '1' : '0').Append('\t')
|
|
.Append(cheat.Name).Append('\t')
|
|
.Append(cheat.SizeAsChar).Append('\t')
|
|
.Append(cheat.TypeAsChar).Append('\t')
|
|
.Append((cheat.BigEndian ?? false) ? '1' : '0').Append('\t')
|
|
.AppendLine();
|
|
}
|
|
}
|
|
|
|
sw.WriteLine(sb.ToString());
|
|
}
|
|
|
|
_currentFileName = path;
|
|
Global.Config.RecentCheats.Add(_currentFileName);
|
|
Changes = false;
|
|
return true;
|
|
}
|
|
catch
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
public bool Load(string path, bool append)
|
|
{
|
|
var file = new FileInfo(path);
|
|
if (file.Exists == false)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!append)
|
|
{
|
|
_currentFileName = path;
|
|
}
|
|
|
|
using (var sr = file.OpenText())
|
|
{
|
|
if (!append)
|
|
{
|
|
Clear();
|
|
}
|
|
|
|
string s;
|
|
while ((s = sr.ReadLine()) != null)
|
|
{
|
|
try
|
|
{
|
|
if (s == "----")
|
|
{
|
|
_cheatList.Add(Cheat.Separator);
|
|
}
|
|
else
|
|
{
|
|
int? compare;
|
|
var size = Watch.WatchSize.Byte;
|
|
var type = Watch.DisplayType.Hex;
|
|
var bigendian = false;
|
|
|
|
if (s.Length < 6)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
var vals = s.Split('\t');
|
|
var address = int.Parse(vals[0], NumberStyles.HexNumber);
|
|
var value = int.Parse(vals[1], NumberStyles.HexNumber);
|
|
|
|
if (vals[2] == "N")
|
|
{
|
|
compare = null;
|
|
}
|
|
else
|
|
{
|
|
compare = int.Parse(vals[2], NumberStyles.HexNumber);
|
|
}
|
|
|
|
var domain = Global.Emulator.MemoryDomains[vals[3]];
|
|
var enabled = vals[4] == "1";
|
|
var name = vals[5];
|
|
|
|
// For backwards compatibility, don't assume these values exist
|
|
if (vals.Length > 6)
|
|
{
|
|
size = Watch.SizeFromChar(vals[6][0]);
|
|
type = Watch.DisplayTypeFromChar(vals[7][0]);
|
|
bigendian = vals[8] == "1";
|
|
}
|
|
|
|
var watch = Watch.GenerateWatch(
|
|
domain,
|
|
address,
|
|
size,
|
|
type,
|
|
name,
|
|
bigendian);
|
|
|
|
Add(new Cheat(watch, value, compare, !Global.Config.DisableCheatsOnLoad && enabled));
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
Changes = false;
|
|
return true;
|
|
}
|
|
|
|
public void Sort(string column, bool reverse)
|
|
{
|
|
switch (column)
|
|
{
|
|
case NAME:
|
|
if (reverse)
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderByDescending(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
|
|
break;
|
|
case ADDRESS:
|
|
if (reverse)
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderByDescending(x => x.Address ?? 0)
|
|
.ThenBy(x => x.Name)
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderBy(x => x.Address ?? 0)
|
|
.ThenBy(x => x.Name)
|
|
.ToList();
|
|
}
|
|
|
|
break;
|
|
case VALUE:
|
|
if (reverse)
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderByDescending(x => x.Value ?? 0)
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderBy(x => x.Value ?? 0)
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
|
|
break;
|
|
case COMPARE:
|
|
if (reverse)
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderByDescending(x => x.Compare ?? 0)
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderBy(x => x.Compare ?? 0)
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
|
|
break;
|
|
case ON:
|
|
if (reverse)
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderByDescending(x => x.Enabled)
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderBy(x => x.Enabled)
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
|
|
break;
|
|
case DOMAIN:
|
|
if (reverse)
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderByDescending(x => x.Domain)
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderBy(x => x.Domain)
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
|
|
break;
|
|
case SIZE:
|
|
if (reverse)
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderByDescending(x => ((int)x.Size))
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderBy(x => ((int)x.Size))
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
|
|
break;
|
|
case ENDIAN:
|
|
if (reverse)
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderByDescending(x => x.BigEndian)
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderBy(x => x.BigEndian)
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
|
|
break;
|
|
case TYPE:
|
|
if (reverse)
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderByDescending(x => x.Type)
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
else
|
|
{
|
|
_cheatList = _cheatList
|
|
.OrderBy(x => x.Type)
|
|
.ThenBy(x => x.Name)
|
|
.ThenBy(x => x.Address ?? 0)
|
|
.ToList();
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void CheatChanged(object sender)
|
|
{
|
|
if (Changed != null)
|
|
{
|
|
Changed(this, new CheatListEventArgs(sender as Cheat));
|
|
}
|
|
|
|
_changes = true;
|
|
}
|
|
|
|
public class CheatListEventArgs : EventArgs
|
|
{
|
|
public CheatListEventArgs(Cheat c)
|
|
{
|
|
Cheat = c;
|
|
}
|
|
|
|
public Cheat Cheat { get; private set; }
|
|
}
|
|
|
|
public const string NAME = "NamesColumn";
|
|
public const string ADDRESS = "AddressColumn";
|
|
public const string VALUE = "ValueColumn";
|
|
public const string COMPARE = "CompareColumn";
|
|
public const string ON = "OnColumn";
|
|
public const string DOMAIN = "DomainColumn";
|
|
public const string SIZE = "SizeColumn";
|
|
public const string ENDIAN = "EndianColumn";
|
|
public const string TYPE = "DisplayTypeColumn";
|
|
}
|
|
}
|