From e98abf182043630fd8fae1a20a3ddfe5fe56a313 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 3 Jan 2022 08:39:43 +0000 Subject: [PATCH] Add Cheat Manager (#2964) * add cheatmanager * use modloader to load cheats for manager * addressed nits --- Ryujinx.HLE/HOS/ModLoader.cs | 15 +- Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs | 8 +- Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs | 1 + Ryujinx.HLE/HOS/TamperMachine.cs | 22 ++- Ryujinx.HLE/Switch.cs | 5 + Ryujinx/Ryujinx.csproj | 2 + Ryujinx/Ui/MainWindow.cs | 14 ++ Ryujinx/Ui/MainWindow.glade | 28 +++- .../Widgets/GameTableContextMenu.Designer.cs | 11 ++ Ryujinx/Ui/Widgets/GameTableContextMenu.cs | 5 + Ryujinx/Ui/Windows/CheatWindow.cs | 155 ++++++++++++++++++ Ryujinx/Ui/Windows/CheatWindow.glade | 135 +++++++++++++++ 12 files changed, 388 insertions(+), 13 deletions(-) create mode 100644 Ryujinx/Ui/Windows/CheatWindow.cs create mode 100644 Ryujinx/Ui/Windows/CheatWindow.glade diff --git a/Ryujinx.HLE/HOS/ModLoader.cs b/Ryujinx.HLE/HOS/ModLoader.cs index 54a9755690..b31798b887 100644 --- a/Ryujinx.HLE/HOS/ModLoader.cs +++ b/Ryujinx.HLE/HOS/ModLoader.cs @@ -664,7 +664,20 @@ namespace Ryujinx.HLE.HOS Logger.Info?.Print(LogClass.ModLoader, $"Installing cheat '{cheat.Name}'"); - tamperMachine.InstallAtmosphereCheat(cheat.Name, cheat.Instructions, tamperInfo, exeAddress); + tamperMachine.InstallAtmosphereCheat(cheat.Name, cheatId, cheat.Instructions, tamperInfo, exeAddress); + } + + EnableCheats(titleId, tamperMachine); + } + + internal void EnableCheats(ulong titleId, TamperMachine tamperMachine) + { + var contentDirectory = FindTitleDir(new DirectoryInfo(Path.Combine(GetModsBasePath(), AmsContentsDir)), $"{titleId:x16}"); + string enabledCheatsPath = Path.Combine(contentDirectory.FullName, CheatDir, "enabled.txt"); + + if (File.Exists(enabledCheatsPath)) + { + tamperMachine.EnableCheats(File.ReadAllLines(enabledCheatsPath)); } } diff --git a/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs b/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs index dac445b0d0..a2aa73a4fa 100644 --- a/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs +++ b/Ryujinx.HLE/HOS/Tamper/AtmosphereProgram.cs @@ -11,6 +11,7 @@ namespace Ryujinx.HLE.HOS.Tamper public string Name { get; } public bool TampersCodeMemory { get; set; } = false; public ITamperedProcess Process { get; } + public bool IsEnabled { get; set; } public AtmosphereProgram(string name, ITamperedProcess process, Parameter pressedKeys, IOperation entryPoint) { @@ -22,8 +23,11 @@ namespace Ryujinx.HLE.HOS.Tamper public void Execute(ControllerKeys pressedKeys) { - _pressedKeys.Value = (long)pressedKeys; - _entryPoint.Execute(); + if (IsEnabled) + { + _pressedKeys.Value = (long)pressedKeys; + _entryPoint.Execute(); + } } } } diff --git a/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs b/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs index 63702bf75b..8458d95d66 100644 --- a/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs +++ b/Ryujinx.HLE/HOS/Tamper/ITamperProgram.cs @@ -4,6 +4,7 @@ namespace Ryujinx.HLE.HOS.Tamper { interface ITamperProgram { + bool IsEnabled { get; set; } string Name { get; } bool TampersCodeMemory { get; set; } ITamperedProcess Process { get; } diff --git a/Ryujinx.HLE/HOS/TamperMachine.cs b/Ryujinx.HLE/HOS/TamperMachine.cs index 6044368e94..016f326f1b 100644 --- a/Ryujinx.HLE/HOS/TamperMachine.cs +++ b/Ryujinx.HLE/HOS/TamperMachine.cs @@ -20,6 +20,7 @@ namespace Ryujinx.HLE.HOS private Thread _tamperThread = null; private ConcurrentQueue _programs = new ConcurrentQueue(); private long _pressedKeys = 0; + private Dictionary _programDictionary = new Dictionary(); private void Activate() { @@ -31,7 +32,7 @@ namespace Ryujinx.HLE.HOS } } - internal void InstallAtmosphereCheat(string name, IEnumerable rawInstructions, ProcessTamperInfo info, ulong exeAddress) + internal void InstallAtmosphereCheat(string name, string buildId, IEnumerable rawInstructions, ProcessTamperInfo info, ulong exeAddress) { if (!CanInstallOnPid(info.Process.Pid)) { @@ -47,6 +48,7 @@ namespace Ryujinx.HLE.HOS program.TampersCodeMemory = false; _programs.Enqueue(program); + _programDictionary.TryAdd($"{buildId}-{name}", program); } Activate(); @@ -65,6 +67,22 @@ namespace Ryujinx.HLE.HOS return true; } + public void EnableCheats(string[] enabledCheats) + { + foreach (var program in _programDictionary.Values) + { + program.IsEnabled = false; + } + + foreach (var cheat in enabledCheats) + { + if (_programDictionary.TryGetValue(cheat, out var program)) + { + program.IsEnabled = true; + } + } + } + private bool IsProcessValid(ITamperedProcess process) { return process.State != ProcessState.Crashed && process.State != ProcessState.Exiting && process.State != ProcessState.Exited; @@ -105,6 +123,8 @@ namespace Ryujinx.HLE.HOS if (!_programs.TryDequeue(out ITamperProgram program)) { // No more programs in the queue. + _programDictionary.Clear(); + return false; } diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index ac7d461127..0dcbc7ec50 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -156,6 +156,11 @@ namespace Ryujinx.HLE return System.GetVolume(); } + public void EnableCheats() + { + FileSystem.ModLoader.EnableCheats(Application.TitleId, TamperMachine); + } + public bool IsAudioMuted() { return System.GetVolume() == 0; diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index 2e1bcf52c6..9374df102e 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -81,6 +81,7 @@ + @@ -106,6 +107,7 @@ + diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index e78d1a46d4..b955dc73b1 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -1553,6 +1553,20 @@ namespace Ryujinx.Ui ToggleExtraWidgets(false); } + private void ManageCheats_Pressed(object sender, EventArgs args) + { + var window = new CheatWindow(_virtualFileSystem, _emulationContext.Application.TitleId, _emulationContext.Application.TitleName); + + window.Destroyed += CheatWindow_Destroyed; + window.Show(); + } + + private void CheatWindow_Destroyed(object sender, EventArgs e) + { + _emulationContext.EnableCheats(); + (sender as CheatWindow).Destroyed -= CheatWindow_Destroyed; + } + private void ManageUserProfiles_Pressed(object sender, EventArgs args) { UserProfilesManagerWindow userProfilesManagerWindow = new UserProfilesManagerWindow(_accountManager, _contentManager, _virtualFileSystem); diff --git a/Ryujinx/Ui/MainWindow.glade b/Ryujinx/Ui/MainWindow.glade index a9ab43e1e3..595786a325 100644 --- a/Ryujinx/Ui/MainWindow.glade +++ b/Ryujinx/Ui/MainWindow.glade @@ -1,5 +1,5 @@ - + @@ -364,7 +364,15 @@ False Hide UI (SHOWUIKEY to show) True - + + + + + + True + False + Manage Cheats + @@ -485,7 +493,7 @@ True True True - + @@ -519,7 +527,7 @@ True False 5 - + RefreshList @@ -582,7 +590,7 @@ True False - + True @@ -615,7 +623,7 @@ True False - + True @@ -647,7 +655,7 @@ True False - + True @@ -655,7 +663,6 @@ start 5 5 - @@ -680,7 +687,7 @@ True False - + True @@ -862,5 +869,8 @@ + + + diff --git a/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs b/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs index 4b903d6cf1..190efd4947 100644 --- a/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs +++ b/Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs @@ -9,6 +9,7 @@ namespace Ryujinx.Ui.Widgets private MenuItem _openSaveBcatDirMenuItem; private MenuItem _manageTitleUpdatesMenuItem; private MenuItem _manageDlcMenuItem; + private MenuItem _manageCheatMenuItem; private MenuItem _openTitleModDirMenuItem; private Menu _extractSubMenu; private MenuItem _extractMenuItem; @@ -69,6 +70,15 @@ namespace Ryujinx.Ui.Widgets }; _manageDlcMenuItem.Activated += ManageDlc_Clicked; + // + // _manageCheatMenuItem + // + _manageCheatMenuItem = new MenuItem("Manage Cheats") + { + TooltipText = "Open the Cheat management window" + }; + _manageCheatMenuItem.Activated += ManageCheats_Clicked; + // // _openTitleModDirMenuItem // @@ -187,6 +197,7 @@ namespace Ryujinx.Ui.Widgets Add(new SeparatorMenuItem()); Add(_manageTitleUpdatesMenuItem); Add(_manageDlcMenuItem); + Add(_manageCheatMenuItem); Add(_openTitleModDirMenuItem); Add(new SeparatorMenuItem()); Add(_manageCacheMenuItem); diff --git a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs index 5ad6e35fe0..c54e16a60a 100644 --- a/Ryujinx/Ui/Widgets/GameTableContextMenu.cs +++ b/Ryujinx/Ui/Widgets/GameTableContextMenu.cs @@ -469,6 +469,11 @@ namespace Ryujinx.Ui.Widgets new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show(); } + private void ManageCheats_Clicked(object sender, EventArgs args) + { + new CheatWindow(_virtualFileSystem, _titleId, _titleName).Show(); + } + private void OpenTitleModDir_Clicked(object sender, EventArgs args) { string modsBasePath = _virtualFileSystem.ModLoader.GetModsBasePath(); diff --git a/Ryujinx/Ui/Windows/CheatWindow.cs b/Ryujinx/Ui/Windows/CheatWindow.cs new file mode 100644 index 0000000000..e4f6c44ebf --- /dev/null +++ b/Ryujinx/Ui/Windows/CheatWindow.cs @@ -0,0 +1,155 @@ +using Gtk; +using Ryujinx.HLE.FileSystem; +using Ryujinx.HLE.HOS; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +using GUI = Gtk.Builder.ObjectAttribute; +using JsonHelper = Ryujinx.Common.Utilities.JsonHelper; + +namespace Ryujinx.Ui.Windows +{ + public class CheatWindow : Window + { + private readonly string _enabledCheatsPath; + private readonly bool _noCheatsFound; + +#pragma warning disable CS0649, IDE0044 + [GUI] Label _baseTitleInfoLabel; + [GUI] TreeView _cheatTreeView; + [GUI] Button _saveButton; +#pragma warning restore CS0649, IDE0044 + + public CheatWindow(VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) : this(new Builder("Ryujinx.Ui.Windows.CheatWindow.glade"), virtualFileSystem, titleId, titleName) { } + + private CheatWindow(Builder builder, VirtualFileSystem virtualFileSystem, ulong titleId, string titleName) : base(builder.GetObject("_cheatWindow").Handle) + { + builder.Autoconnect(this); + _baseTitleInfoLabel.Text = $"Cheats Available for {titleName} [{titleId:X16}]"; + + string modsBasePath = virtualFileSystem.ModLoader.GetModsBasePath(); + string titleModsPath = virtualFileSystem.ModLoader.GetTitleDir(modsBasePath, titleId.ToString("X16")); + + _enabledCheatsPath = System.IO.Path.Combine(titleModsPath, "cheats", "enabled.txt"); + + _cheatTreeView.Model = new TreeStore(typeof(bool), typeof(string), typeof(string), typeof(string)); + + CellRendererToggle enableToggle = new CellRendererToggle(); + enableToggle.Toggled += (sender, args) => + { + _cheatTreeView.Model.GetIter(out TreeIter treeIter, new TreePath(args.Path)); + bool newValue = !(bool)_cheatTreeView.Model.GetValue(treeIter, 0); + _cheatTreeView.Model.SetValue(treeIter, 0, newValue); + + if (_cheatTreeView.Model.IterChildren(out TreeIter childIter, treeIter)) + { + do + { + _cheatTreeView.Model.SetValue(childIter, 0, newValue); + } + while (_cheatTreeView.Model.IterNext(ref childIter)); + } + }; + + _cheatTreeView.AppendColumn("Enabled", enableToggle, "active", 0); + _cheatTreeView.AppendColumn("Name", new CellRendererText(), "text", 1); + _cheatTreeView.AppendColumn("Path", new CellRendererText(), "text", 2); + + var buildIdColumn = _cheatTreeView.AppendColumn("Build Id", new CellRendererText(), "text", 3); + buildIdColumn.Visible = false; + + string[] enabled = { }; + + if (File.Exists(_enabledCheatsPath)) + { + enabled = File.ReadAllLines(_enabledCheatsPath); + } + + int cheatAdded = 0; + + var mods = new ModLoader.ModCache(); + + ModLoader.QueryContentsDir(mods, new DirectoryInfo(System.IO.Path.Combine(modsBasePath, "contents")), titleId); + + string currentCheatFile = string.Empty; + string buildId = string.Empty; + TreeIter parentIter = default; + + foreach (var cheat in mods.Cheats) + { + if (cheat.Path.FullName != currentCheatFile) + { + currentCheatFile = cheat.Path.FullName; + string parentPath = currentCheatFile.Replace(titleModsPath, ""); + + buildId = System.IO.Path.GetFileNameWithoutExtension(currentCheatFile); + parentIter = ((TreeStore)_cheatTreeView.Model).AppendValues(false, buildId, parentPath, ""); + } + + string cleanName = cheat.Name.Substring(1, cheat.Name.Length - 8); + ((TreeStore)_cheatTreeView.Model).AppendValues(parentIter, enabled.Contains($"{buildId}-{cheat.Name}"), cleanName, "", buildId); + + cheatAdded++; + } + + if (cheatAdded == 0) + { + ((TreeStore)_cheatTreeView.Model).AppendValues(false, "No Cheats Found", "", ""); + _cheatTreeView.GetColumn(0).Visible = false; + + _noCheatsFound = true; + + _saveButton.Visible = false; + } + + _cheatTreeView.ExpandAll(); + } + + private void SaveButton_Clicked(object sender, EventArgs args) + { + if (_noCheatsFound) + { + return; + } + + List enabledCheats = new List(); + + if (_cheatTreeView.Model.GetIterFirst(out TreeIter parentIter)) + { + do + { + if (_cheatTreeView.Model.IterChildren(out TreeIter childIter, parentIter)) + { + do + { + var enabled = (bool)_cheatTreeView.Model.GetValue(childIter, 0); + + if (enabled) + { + var name = _cheatTreeView.Model.GetValue(childIter, 1).ToString(); + var buildId = _cheatTreeView.Model.GetValue(childIter, 3).ToString(); + + enabledCheats.Add($"{buildId}-<{name} Cheat>"); + } + } + while (_cheatTreeView.Model.IterNext(ref childIter)); + } + } + while (_cheatTreeView.Model.IterNext(ref parentIter)); + } + + Directory.CreateDirectory(System.IO.Path.GetDirectoryName(_enabledCheatsPath)); + + File.WriteAllLines(_enabledCheatsPath, enabledCheats); + + Dispose(); + } + + private void CancelButton_Clicked(object sender, EventArgs args) + { + Dispose(); + } + } +} diff --git a/Ryujinx/Ui/Windows/CheatWindow.glade b/Ryujinx/Ui/Windows/CheatWindow.glade new file mode 100644 index 0000000000..37b1cbe078 --- /dev/null +++ b/Ryujinx/Ui/Windows/CheatWindow.glade @@ -0,0 +1,135 @@ + + + + + + False + Ryujinx - Cheat Manager + 440 + 550 + + + True + False + vertical + + + True + False + vertical + + + True + False + 10 + 10 + Available Cheats + + + False + True + 0 + + + + + True + True + 10 + 10 + in + + + True + False + + + True + True + + + + + + + + + + True + True + 1 + + + + + True + True + 0 + + + + + True + False + + + True + False + 10 + 10 + end + + + Save + True + True + True + 10 + 2 + 2 + + + + True + True + 0 + + + + + Cancel + True + True + True + 10 + 2 + 2 + + + + True + True + 1 + + + + + True + True + 1 + + + + + False + True + 1 + + + + + + + + +