using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.IO; using System.Linq; using System.Windows.Forms; using BizHawk.Emulation.Common; using BizHawk.Client.Common; using BizHawk.Client.EmuHawk.ToolExtensions; using BizHawk.Client.EmuHawk.WinFormExtensions; namespace BizHawk.Client.EmuHawk { public partial class Cheats : ToolFormBase, IToolForm { private const string NameColumn = "NamesColumn"; private const string AddressColumn = "AddressColumn"; private const string ValueColumn = "ValueColumn"; private const string CompareColumn = "CompareColumn"; private const string OnColumn = "OnColumn"; private const string DomainColumn = "DomainColumn"; private const string SizeColumn = "SizeColumn"; private const string EndianColumn = "EndianColumn"; private const string TypeColumn = "DisplayTypeColumn"; private const string ComparisonTypeColumn = "ComparisonTypeColumn"; private int _defaultWidth; private int _defaultHeight; private string _sortedColumn; private bool _sortReverse; public Cheats() { InitializeComponent(); Settings = new CheatsSettings(); Closing += (o, e) => { SaveConfigSettings(); }; CheatListView.QueryItemText += CheatListView_QueryItemText; CheatListView.QueryItemBkColor += CheatListView_QueryItemBkColor; _sortedColumn = ""; _sortReverse = false; } [RequiredService] private IMemoryDomains Core { get; set; } [ConfigPersist] public CheatsSettings Settings { get; set; } public bool UpdateBefore => false; public void NewUpdate(ToolFormUpdateType type) { } public void UpdateValues() { // Do nothing } public void FastUpdate() { // Do nothing } public void Restart() { CheatEditor.MemoryDomains = Core; CheatEditor.Restart(); } /// /// Tools that want to refresh the cheats list should call this, not UpdateValues /// public void UpdateDialog() { CheatListView.RowCount = Global.CheatList.Count; TotalLabel.Text = $"{Global.CheatList.CheatCount} {(Global.CheatList.CheatCount == 1 ? "cheat" : "cheats")} {Global.CheatList.ActiveCount} active"; } private void LoadFileFromRecent(string path) { var askResult = !Global.CheatList.Changes || AskSaveChanges(); if (askResult) { var loadResult = Global.CheatList.Load(path, append: false); if (!loadResult) { Global.Config.RecentCheats.HandleLoadError(path); } else { Global.Config.RecentCheats.Add(path); UpdateDialog(); UpdateMessageLabel(); } } } private void UpdateMessageLabel(bool saved = false) { MessageLabel.Text = saved ? $"{Path.GetFileName(Global.CheatList.CurrentFileName)} saved." : Global.CheatList.Changes ? $"{Path.GetFileName(Global.CheatList.CurrentFileName)} *" : Path.GetFileName(Global.CheatList.CurrentFileName); } public bool AskSaveChanges() { return true; } private void LoadFile(FileSystemInfo file, bool append) { if (file != null) { var result = true; if (Global.CheatList.Changes) { result = AskSaveChanges(); } if (result) { Global.CheatList.Load(file.FullName, append); UpdateDialog(); UpdateMessageLabel(); Global.Config.RecentCheats.Add(Global.CheatList.CurrentFileName); } } } private static bool SaveAs() { var file = SaveFileDialog( Global.CheatList.CurrentFileName, PathManager.GetCheatsPath(Global.Game), "Cheat Files", "cht"); return file != null && Global.CheatList.SaveFile(file.FullName); } private void Cheats_Load(object sender, EventArgs e) { // Hack for previous config settings if (Settings.Columns.Any(c => string.IsNullOrWhiteSpace(c.Text))) { Settings = new CheatsSettings(); } TopMost = Settings.TopMost; CheatEditor.MemoryDomains = Core; LoadConfigSettings(); CheatsMenu.Items.Add(CheatListView.ToColumnsMenu(ColumnToggleCallback)); ToggleGameGenieButton(); CheatEditor.SetAddEvent(AddCheat); CheatEditor.SetEditEvent(EditCheat); UpdateDialog(); } private void SetColumns() { foreach (var column in Settings.Columns) { if (CheatListView.AllColumns[column.Name] == null) { CheatListView.AllColumns.Add(column); } } } private void ColumnToggleCallback() { Settings.Columns = CheatListView.AllColumns; } private void ToggleGameGenieButton() { GameGenieToolbarSeparator.Visible = LoadGameGenieToolbarItem.Visible = GlobalWin.Tools.IsAvailable(); } private void AddCheat() { Global.CheatList.Add(CheatEditor.GetCheat()); UpdateDialog(); UpdateMessageLabel(); } private void EditCheat() { var newCheat = CheatEditor.GetCheat(); if (!newCheat.IsSeparator) // If a separator comes from the cheat editor something must have been invalid { Global.CheatList.Exchange(CheatEditor.OriginalCheat, newCheat); UpdateDialog(); UpdateMessageLabel(); } } private void SaveConfigSettings() { Settings.Columns = CheatListView.AllColumns; if (WindowState == FormWindowState.Normal) { Settings.Wndx = Location.X; Settings.Wndy = Location.Y; Settings.Width = Right - Left; Settings.Height = Bottom - Top; } } private void LoadConfigSettings() { _defaultWidth = Size.Width; _defaultHeight = Size.Height; if (Settings.UseWindowPosition && IsOnScreen(Settings.TopLeft)) { Location = Settings.WindowPosition; } if (Settings.UseWindowSize) { Size = Settings.WindowSize; } CheatListView.AllColumns.Clear(); SetColumns(); } private void CheatListView_QueryItemText(int index, RollColumn column, out string text, ref int offsetX, ref int offsetY) { text = ""; if (index >= Global.CheatList.Count || Global.CheatList[index].IsSeparator) { return; } var columnName = column.Name; switch (columnName) { case NameColumn: text = Global.CheatList[index].Name; break; case AddressColumn: text = Global.CheatList[index].AddressStr; break; case ValueColumn: text = Global.CheatList[index].ValueStr; break; case CompareColumn: text = Global.CheatList[index].CompareStr; break; case OnColumn: text = Global.CheatList[index].Enabled ? "*" : ""; break; case DomainColumn: text = Global.CheatList[index].Domain.Name; break; case SizeColumn: text = Global.CheatList[index].Size.ToString(); break; case EndianColumn: text = (Global.CheatList[index].BigEndian ?? false) ? "Big" : "Little"; break; case TypeColumn: text = Watch.DisplayTypeToString(Global.CheatList[index].Type); break; case ComparisonTypeColumn: switch (Global.CheatList[index].ComparisonType) { case Cheat.CompareType.None: text = ""; break; case Cheat.CompareType.Equal: text = "="; break; case Cheat.CompareType.GreaterThan: text = ">"; break; case Cheat.CompareType.GreaterThanOrEqual: text = ">="; break; case Cheat.CompareType.LessThan: text = "<"; break; case Cheat.CompareType.LessThanOrEqual: text = "<="; break; case Cheat.CompareType.NotEqual: text = "!="; break; default: text = ""; break; } break; } } private void CheatListView_QueryItemBkColor(int index, RollColumn column, ref Color color) { if (index < Global.CheatList.Count) { if (Global.CheatList[index].IsSeparator) { color = BackColor; } else if (Global.CheatList[index].Enabled) { color = Color.LightCyan; } } } private IEnumerable SelectedIndices => CheatListView.SelectedRows; private IEnumerable SelectedItems => SelectedIndices.Select(index => Global.CheatList[index]); private IEnumerable SelectedCheats => SelectedItems.Where(x => !x.IsSeparator); private void DoSelectedIndexChange() { if (SelectedCheats.Any()) { var cheat = SelectedCheats.First(); CheatEditor.SetCheat(cheat); CheatGroupBox.Text = $"Editing Cheat {cheat.Name} - {cheat.AddressStr}"; } else { CheatEditor.ClearForm(); CheatGroupBox.Text = "New Cheat"; } } private void StartNewList() { var result = !Global.CheatList.Changes || AskSaveChanges(); if (result) { Global.CheatList.NewList(ToolManager.GenerateDefaultCheatFilename()); UpdateDialog(); UpdateMessageLabel(); ToggleGameGenieButton(); } } private void NewList() { var result = !Global.CheatList.Changes || AskSaveChanges(); if (result) { StartNewList(); } } #region Events #region File private void FileSubMenu_DropDownOpened(object sender, EventArgs e) { SaveMenuItem.Enabled = Global.CheatList.Changes; } private void RecentSubMenu_DropDownOpened(object sender, EventArgs e) { RecentSubMenu.DropDownItems.Clear(); RecentSubMenu.DropDownItems.AddRange( Global.Config.RecentCheats.RecentMenu(LoadFileFromRecent)); } private void NewMenuItem_Click(object sender, EventArgs e) { NewList(); } private void OpenMenuItem_Click(object sender, EventArgs e) { var file = OpenFileDialog( Global.CheatList.CurrentFileName, PathManager.GetCheatsPath(Global.Game), "Cheat Files", "cht"); LoadFile(file, append: sender == AppendMenuItem); } private void SaveMenuItem_Click(object sender, EventArgs e) { if (Global.CheatList.Changes) { if (Global.CheatList.Save()) { UpdateMessageLabel(saved: true); } } else { SaveAsMenuItem_Click(sender, e); } } private void SaveAsMenuItem_Click(object sender, EventArgs e) { if (SaveAs()) { UpdateMessageLabel(saved: true); } } private void ExitMenuItem_Click(object sender, EventArgs e) { Close(); } #endregion #region Cheats private void CheatsSubMenu_DropDownOpened(object sender, EventArgs e) { RemoveCheatMenuItem.Enabled = MoveUpMenuItem.Enabled = MoveDownMenuItem.Enabled = ToggleMenuItem.Enabled = SelectedIndices.Any(); // Always leave enabled even if no cheats enabled. This way the hotkey will always work however a new cheat is enabled // DisableAllCheatsMenuItem.Enabled = Global.CheatList.ActiveCount > 0; GameGenieSeparator.Visible = OpenGameGenieEncoderDecoderMenuItem.Visible = GlobalWin.Tools.IsAvailable(); } private void RemoveCheatMenuItem_Click(object sender, EventArgs e) { var items = SelectedItems.ToList(); if (items.Any()) { foreach (var item in items) { Global.CheatList.Remove(item); } CheatListView.DeselectAll(); UpdateDialog(); } } private void InsertSeparatorMenuItem_Click(object sender, EventArgs e) { if (SelectedIndices.Any()) { Global.CheatList.Insert(SelectedIndices.Max(), Cheat.Separator); } else { Global.CheatList.Add(Cheat.Separator); } UpdateDialog(); UpdateMessageLabel(); } private void MoveUpMenuItem_Click(object sender, EventArgs e) { var indices = SelectedIndices.ToList(); if (indices.Count == 0 || indices[0] == 0) { return; } foreach (var index in indices) { var cheat = Global.CheatList[index]; Global.CheatList.Remove(cheat); Global.CheatList.Insert(index - 1, cheat); } var newIndices = indices.Select(t => t - 1); CheatListView.DeselectAll(); foreach (var index in newIndices) { CheatListView.SelectRow(index, true); } UpdateMessageLabel(); UpdateDialog(); } private void MoveDownMenuItem_Click(object sender, EventArgs e) { var indices = SelectedIndices.ToList(); if (indices.Count == 0 || indices.Last() == Global.CheatList.Count - 1) { return; } for (var i = indices.Count - 1; i >= 0; i--) { var cheat = Global.CheatList[indices[i]]; Global.CheatList.Remove(cheat); Global.CheatList.Insert(indices[i] + 1, cheat); } UpdateMessageLabel(); var newIndices = indices.Select(t => t + 1); CheatListView.DeselectAll(); foreach (var index in newIndices) { CheatListView.SelectRow(index, true); } UpdateDialog(); } private void SelectAllMenuItem_Click(object sender, EventArgs e) { CheatListView.SelectAll(); } private void ToggleMenuItem_Click(object sender, EventArgs e) { foreach (var x in SelectedCheats) { x.Toggle(); } CheatListView.Refresh(); } private void DisableAllCheatsMenuItem_Click(object sender, EventArgs e) { Global.CheatList.DisableAll(); } private void OpenGameGenieEncoderDecoderMenuItem_Click(object sender, EventArgs e) { GlobalWin.Tools.LoadGameGenieEc(); } #endregion #region Options private void OptionsSubMenu_DropDownOpened(object sender, EventArgs e) { AlwaysLoadCheatsMenuItem.Checked = Global.Config.LoadCheatFileByGame; AutoSaveCheatsMenuItem.Checked = Global.Config.CheatsAutoSaveOnClose; DisableCheatsOnLoadMenuItem.Checked = Global.Config.DisableCheatsOnLoad; AutoloadMenuItem.Checked = Global.Config.RecentCheats.AutoLoad; SaveWindowPositionMenuItem.Checked = Settings.SaveWindowPosition; AlwaysOnTopMenuItem.Checked = Settings.TopMost; FloatingWindowMenuItem.Checked = Settings.FloatingWindow; } private void AlwaysLoadCheatsMenuItem_Click(object sender, EventArgs e) { Global.Config.LoadCheatFileByGame ^= true; } private void AutoSaveCheatsMenuItem_Click(object sender, EventArgs e) { Global.Config.CheatsAutoSaveOnClose ^= true; } private void CheatsOnOffLoadMenuItem_Click(object sender, EventArgs e) { Global.Config.DisableCheatsOnLoad ^= true; } private void AutoloadMenuItem_Click(object sender, EventArgs e) { Global.Config.RecentCheats.AutoLoad ^= true; } private void SaveWindowPositionMenuItem_Click(object sender, EventArgs e) { Settings.SaveWindowPosition ^= true; } private void AlwaysOnTopMenuItem_Click(object sender, EventArgs e) { Settings.TopMost ^= true; } private void FloatingWindowMenuItem_Click(object sender, EventArgs e) { Settings.FloatingWindow ^= true; RefreshFloatingWindowControl(Settings.FloatingWindow); } private void RestoreDefaultsMenuItem_Click(object sender, EventArgs e) { Size = new Size(_defaultWidth, _defaultHeight); Settings = new CheatsSettings(); CheatsMenu.Items.Remove( CheatsMenu.Items .OfType() .First(x => x.Name == "GeneratedColumnsSubMenu")); CheatsMenu.Items.Add(CheatListView.ToColumnsMenu(ColumnToggleCallback)); Global.Config.DisableCheatsOnLoad = false; Global.Config.LoadCheatFileByGame = true; Global.Config.CheatsAutoSaveOnClose = true; RefreshFloatingWindowControl(Settings.FloatingWindow); CheatListView.AllColumns.Clear(); SetColumns(); } #endregion #region ListView and Dialog Events private void CheatListView_DoubleClick(object sender, EventArgs e) { ToggleMenuItem_Click(sender, e); } private void CheatListView_KeyDown(object sender, KeyEventArgs e) { if (e.KeyCode == Keys.Delete && !e.Control && !e.Alt && !e.Shift) { RemoveCheatMenuItem_Click(sender, e); } else if (e.KeyCode == Keys.A && e.Control && !e.Alt && !e.Shift) { SelectAllMenuItem_Click(null, null); } } private void CheatListView_SelectedIndexChanged(object sender, EventArgs e) { DoSelectedIndexChange(); } private void CheatListView_ColumnClick(object sender, InputRoll.ColumnClickEventArgs e) { var column = e.Column; if (column.Name != _sortedColumn) { _sortReverse = false; } Global.CheatList.Sort(column.Name, _sortReverse); _sortedColumn = column.Name; _sortReverse ^= true; UpdateDialog(); } private void NewCheatForm_DragDrop(object sender, DragEventArgs e) { var filePaths = (string[])e.Data.GetData(DataFormats.FileDrop); if (Path.GetExtension(filePaths[0]) == ".cht") { LoadFile(new FileInfo(filePaths[0]), append: false); UpdateDialog(); UpdateMessageLabel(); } } private void NewCheatForm_DragEnter(object sender, DragEventArgs e) { e.Effect = e.Data.GetDataPresent(DataFormats.FileDrop) ? DragDropEffects.Copy : DragDropEffects.None; } private void CheatsContextMenu_Opening(object sender, CancelEventArgs e) { ToggleContextMenuItem.Enabled = RemoveContextMenuItem.Enabled = SelectedCheats.Any(); DisableAllContextMenuItem.Enabled = Global.CheatList.ActiveCount > 0; } private void ViewInHexEditorContextMenuItem_Click(object sender, EventArgs e) { var selected = SelectedCheats.ToList(); if (selected.Any()) { GlobalWin.Tools.Load(); if (selected.Select(x => x.Domain).Distinct().Count() > 1) { ViewInHexEditor(selected[0].Domain, new List { selected.First().Address ?? 0 }, selected.First().Size); } else { ViewInHexEditor(selected.First().Domain, selected.Select(x => x.Address ?? 0), selected.First().Size); } } } protected override void OnShown(EventArgs e) { RefreshFloatingWindowControl(Settings.FloatingWindow); base.OnShown(e); } #endregion #endregion public class CheatsSettings : ToolDialogSettings { public CheatsSettings() { Columns = new List { new RollColumn { Text = "Names", Name = NameColumn, Visible = true, Width = 128, Type = ColumnType.Text }, new RollColumn { Text = "Address", Name = AddressColumn, Visible = true, Width = 60, Type = ColumnType.Text }, new RollColumn { Text = "Value", Name = ValueColumn, Visible = true, Width = 59, Type = ColumnType.Text }, new RollColumn { Text = "Compare", Name = CompareColumn, Visible = true, Width = 63, Type = ColumnType.Text }, new RollColumn { Text = "Compare Type", Name = ComparisonTypeColumn, Visible = true, Width = 98, Type = ColumnType.Text }, new RollColumn { Text = "On", Name = OnColumn, Visible = false, Width = 28, Type = ColumnType.Text }, new RollColumn { Text = "Size", Name = SizeColumn, Visible = true, Width = 55, Type = ColumnType.Text }, new RollColumn { Text = "Endian", Name = EndianColumn, Visible = false, Width = 55, Type = ColumnType.Text }, new RollColumn { Text = "Display Type", Name = TypeColumn, Visible = false, Width = 88, Type = ColumnType.Text } }; } public List Columns { get; set; } } } }