Add Cheat Manager (#2964)
* add cheatmanager * use modloader to load cheats for manager * addressed nits
This commit is contained in:
parent
dc8a1d5cba
commit
e98abf1820
12 changed files with 388 additions and 13 deletions
|
@ -664,7 +664,20 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
Logger.Info?.Print(LogClass.ModLoader, $"Installing cheat '{cheat.Name}'");
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ namespace Ryujinx.HLE.HOS.Tamper
|
||||||
public string Name { get; }
|
public string Name { get; }
|
||||||
public bool TampersCodeMemory { get; set; } = false;
|
public bool TampersCodeMemory { get; set; } = false;
|
||||||
public ITamperedProcess Process { get; }
|
public ITamperedProcess Process { get; }
|
||||||
|
public bool IsEnabled { get; set; }
|
||||||
|
|
||||||
public AtmosphereProgram(string name, ITamperedProcess process, Parameter<long> pressedKeys, IOperation entryPoint)
|
public AtmosphereProgram(string name, ITamperedProcess process, Parameter<long> pressedKeys, IOperation entryPoint)
|
||||||
{
|
{
|
||||||
|
@ -22,8 +23,11 @@ namespace Ryujinx.HLE.HOS.Tamper
|
||||||
|
|
||||||
public void Execute(ControllerKeys pressedKeys)
|
public void Execute(ControllerKeys pressedKeys)
|
||||||
{
|
{
|
||||||
_pressedKeys.Value = (long)pressedKeys;
|
if (IsEnabled)
|
||||||
_entryPoint.Execute();
|
{
|
||||||
|
_pressedKeys.Value = (long)pressedKeys;
|
||||||
|
_entryPoint.Execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace Ryujinx.HLE.HOS.Tamper
|
||||||
{
|
{
|
||||||
interface ITamperProgram
|
interface ITamperProgram
|
||||||
{
|
{
|
||||||
|
bool IsEnabled { get; set; }
|
||||||
string Name { get; }
|
string Name { get; }
|
||||||
bool TampersCodeMemory { get; set; }
|
bool TampersCodeMemory { get; set; }
|
||||||
ITamperedProcess Process { get; }
|
ITamperedProcess Process { get; }
|
||||||
|
|
|
@ -20,6 +20,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
private Thread _tamperThread = null;
|
private Thread _tamperThread = null;
|
||||||
private ConcurrentQueue<ITamperProgram> _programs = new ConcurrentQueue<ITamperProgram>();
|
private ConcurrentQueue<ITamperProgram> _programs = new ConcurrentQueue<ITamperProgram>();
|
||||||
private long _pressedKeys = 0;
|
private long _pressedKeys = 0;
|
||||||
|
private Dictionary<string, ITamperProgram> _programDictionary = new Dictionary<string, ITamperProgram>();
|
||||||
|
|
||||||
private void Activate()
|
private void Activate()
|
||||||
{
|
{
|
||||||
|
@ -31,7 +32,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void InstallAtmosphereCheat(string name, IEnumerable<string> rawInstructions, ProcessTamperInfo info, ulong exeAddress)
|
internal void InstallAtmosphereCheat(string name, string buildId, IEnumerable<string> rawInstructions, ProcessTamperInfo info, ulong exeAddress)
|
||||||
{
|
{
|
||||||
if (!CanInstallOnPid(info.Process.Pid))
|
if (!CanInstallOnPid(info.Process.Pid))
|
||||||
{
|
{
|
||||||
|
@ -47,6 +48,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
program.TampersCodeMemory = false;
|
program.TampersCodeMemory = false;
|
||||||
|
|
||||||
_programs.Enqueue(program);
|
_programs.Enqueue(program);
|
||||||
|
_programDictionary.TryAdd($"{buildId}-{name}", program);
|
||||||
}
|
}
|
||||||
|
|
||||||
Activate();
|
Activate();
|
||||||
|
@ -65,6 +67,22 @@ namespace Ryujinx.HLE.HOS
|
||||||
return true;
|
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)
|
private bool IsProcessValid(ITamperedProcess process)
|
||||||
{
|
{
|
||||||
return process.State != ProcessState.Crashed && process.State != ProcessState.Exiting && process.State != ProcessState.Exited;
|
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))
|
if (!_programs.TryDequeue(out ITamperProgram program))
|
||||||
{
|
{
|
||||||
// No more programs in the queue.
|
// No more programs in the queue.
|
||||||
|
_programDictionary.Clear();
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -156,6 +156,11 @@ namespace Ryujinx.HLE
|
||||||
return System.GetVolume();
|
return System.GetVolume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void EnableCheats()
|
||||||
|
{
|
||||||
|
FileSystem.ModLoader.EnableCheats(Application.TitleId, TamperMachine);
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsAudioMuted()
|
public bool IsAudioMuted()
|
||||||
{
|
{
|
||||||
return System.GetVolume() == 0;
|
return System.GetVolume() == 0;
|
||||||
|
|
|
@ -81,6 +81,7 @@
|
||||||
<None Remove="Ui\Resources\Logo_Ryujinx.png" />
|
<None Remove="Ui\Resources\Logo_Ryujinx.png" />
|
||||||
<None Remove="Ui\Resources\Logo_Twitter.png" />
|
<None Remove="Ui\Resources\Logo_Twitter.png" />
|
||||||
<None Remove="Ui\Widgets\ProfileDialog.glade" />
|
<None Remove="Ui\Widgets\ProfileDialog.glade" />
|
||||||
|
<None Remove="Ui\Windows\CheatWindow.glade" />
|
||||||
<None Remove="Ui\Windows\ControllerWindow.glade" />
|
<None Remove="Ui\Windows\ControllerWindow.glade" />
|
||||||
<None Remove="Ui\Windows\DlcWindow.glade" />
|
<None Remove="Ui\Windows\DlcWindow.glade" />
|
||||||
<None Remove="Ui\Windows\SettingsWindow.glade" />
|
<None Remove="Ui\Windows\SettingsWindow.glade" />
|
||||||
|
@ -106,6 +107,7 @@
|
||||||
<EmbeddedResource Include="Ui\Resources\Logo_Ryujinx.png" />
|
<EmbeddedResource Include="Ui\Resources\Logo_Ryujinx.png" />
|
||||||
<EmbeddedResource Include="Ui\Resources\Logo_Twitter.png" />
|
<EmbeddedResource Include="Ui\Resources\Logo_Twitter.png" />
|
||||||
<EmbeddedResource Include="Ui\Widgets\ProfileDialog.glade" />
|
<EmbeddedResource Include="Ui\Widgets\ProfileDialog.glade" />
|
||||||
|
<EmbeddedResource Include="Ui\Windows\CheatWindow.glade" />
|
||||||
<EmbeddedResource Include="Ui\Windows\ControllerWindow.glade" />
|
<EmbeddedResource Include="Ui\Windows\ControllerWindow.glade" />
|
||||||
<EmbeddedResource Include="Ui\Windows\DlcWindow.glade" />
|
<EmbeddedResource Include="Ui\Windows\DlcWindow.glade" />
|
||||||
<EmbeddedResource Include="Ui\Windows\SettingsWindow.glade" />
|
<EmbeddedResource Include="Ui\Windows\SettingsWindow.glade" />
|
||||||
|
|
|
@ -1553,6 +1553,20 @@ namespace Ryujinx.Ui
|
||||||
ToggleExtraWidgets(false);
|
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)
|
private void ManageUserProfiles_Pressed(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
UserProfilesManagerWindow userProfilesManagerWindow = new UserProfilesManagerWindow(_accountManager, _contentManager, _virtualFileSystem);
|
UserProfilesManagerWindow userProfilesManagerWindow = new UserProfilesManagerWindow(_accountManager, _contentManager, _virtualFileSystem);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<!-- Generated with glade 3.38.2 -->
|
<!-- Generated with glade 3.21.0 -->
|
||||||
<interface>
|
<interface>
|
||||||
<requires lib="gtk+" version="3.20"/>
|
<requires lib="gtk+" version="3.20"/>
|
||||||
<object class="GtkApplicationWindow" id="_mainWin">
|
<object class="GtkApplicationWindow" id="_mainWin">
|
||||||
|
@ -364,7 +364,15 @@
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="label" translatable="yes">Hide UI (SHOWUIKEY to show)</property>
|
<property name="label" translatable="yes">Hide UI (SHOWUIKEY to show)</property>
|
||||||
<property name="use_underline">True</property>
|
<property name="use_underline">True</property>
|
||||||
<signal name="activate" handler="HideUi_Pressed" swapped="no" />
|
<signal name="activate" handler="HideUi_Pressed" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkMenuItem" id="_manageCheats">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="label" translatable="yes">Manage Cheats</property>
|
||||||
|
<signal name="activate" handler="ManageCheats_Pressed" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
@ -485,7 +493,7 @@
|
||||||
<property name="can_focus">True</property>
|
<property name="can_focus">True</property>
|
||||||
<property name="reorderable">True</property>
|
<property name="reorderable">True</property>
|
||||||
<property name="hover_selection">True</property>
|
<property name="hover_selection">True</property>
|
||||||
<signal name="row_activated" handler="Row_Activated" swapped="no"/>
|
<signal name="row-activated" handler="Row_Activated" swapped="no"/>
|
||||||
<child internal-child="selection">
|
<child internal-child="selection">
|
||||||
<object class="GtkTreeSelection" id="_gameTableSelection"/>
|
<object class="GtkTreeSelection" id="_gameTableSelection"/>
|
||||||
</child>
|
</child>
|
||||||
|
@ -519,7 +527,7 @@
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<property name="margin_left">5</property>
|
<property name="margin_left">5</property>
|
||||||
<signal name="button_release_event" handler="RefreshList_Pressed" swapped="no"/>
|
<signal name="button-release-event" handler="RefreshList_Pressed" swapped="no"/>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImage">
|
<object class="GtkImage">
|
||||||
<property name="name">RefreshList</property>
|
<property name="name">RefreshList</property>
|
||||||
|
@ -582,7 +590,7 @@
|
||||||
<object class="GtkEventBox">
|
<object class="GtkEventBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<signal name="button_release_event" handler="VSyncStatus_Clicked" swapped="no"/>
|
<signal name="button-release-event" handler="VSyncStatus_Clicked" swapped="no"/>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="_vSyncStatus">
|
<object class="GtkLabel" id="_vSyncStatus">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -615,7 +623,7 @@
|
||||||
<object class="GtkEventBox">
|
<object class="GtkEventBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<signal name="button_release_event" handler="DockedMode_Clicked" swapped="no"/>
|
<signal name="button-release-event" handler="DockedMode_Clicked" swapped="no"/>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="_dockedMode">
|
<object class="GtkLabel" id="_dockedMode">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -647,7 +655,7 @@
|
||||||
<object class="GtkEventBox">
|
<object class="GtkEventBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<signal name="button_release_event" handler="VolumeStatus_Clicked" swapped="no"/>
|
<signal name="button-release-event" handler="VolumeStatus_Clicked" swapped="no"/>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="_volumeStatus">
|
<object class="GtkLabel" id="_volumeStatus">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -655,7 +663,6 @@
|
||||||
<property name="halign">start</property>
|
<property name="halign">start</property>
|
||||||
<property name="margin_left">5</property>
|
<property name="margin_left">5</property>
|
||||||
<property name="margin_right">5</property>
|
<property name="margin_right">5</property>
|
||||||
<property name="label" translatable="yes"></property>
|
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
@ -680,7 +687,7 @@
|
||||||
<object class="GtkEventBox">
|
<object class="GtkEventBox">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
<signal name="button_release_event" handler="AspectRatio_Clicked" swapped="no"/>
|
<signal name="button-release-event" handler="AspectRatio_Clicked" swapped="no"/>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkLabel" id="_aspectRatio">
|
<object class="GtkLabel" id="_aspectRatio">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
|
@ -862,5 +869,8 @@
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
|
<child type="titlebar">
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
</object>
|
</object>
|
||||||
</interface>
|
</interface>
|
||||||
|
|
11
Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs
generated
11
Ryujinx/Ui/Widgets/GameTableContextMenu.Designer.cs
generated
|
@ -9,6 +9,7 @@ namespace Ryujinx.Ui.Widgets
|
||||||
private MenuItem _openSaveBcatDirMenuItem;
|
private MenuItem _openSaveBcatDirMenuItem;
|
||||||
private MenuItem _manageTitleUpdatesMenuItem;
|
private MenuItem _manageTitleUpdatesMenuItem;
|
||||||
private MenuItem _manageDlcMenuItem;
|
private MenuItem _manageDlcMenuItem;
|
||||||
|
private MenuItem _manageCheatMenuItem;
|
||||||
private MenuItem _openTitleModDirMenuItem;
|
private MenuItem _openTitleModDirMenuItem;
|
||||||
private Menu _extractSubMenu;
|
private Menu _extractSubMenu;
|
||||||
private MenuItem _extractMenuItem;
|
private MenuItem _extractMenuItem;
|
||||||
|
@ -69,6 +70,15 @@ namespace Ryujinx.Ui.Widgets
|
||||||
};
|
};
|
||||||
_manageDlcMenuItem.Activated += ManageDlc_Clicked;
|
_manageDlcMenuItem.Activated += ManageDlc_Clicked;
|
||||||
|
|
||||||
|
//
|
||||||
|
// _manageCheatMenuItem
|
||||||
|
//
|
||||||
|
_manageCheatMenuItem = new MenuItem("Manage Cheats")
|
||||||
|
{
|
||||||
|
TooltipText = "Open the Cheat management window"
|
||||||
|
};
|
||||||
|
_manageCheatMenuItem.Activated += ManageCheats_Clicked;
|
||||||
|
|
||||||
//
|
//
|
||||||
// _openTitleModDirMenuItem
|
// _openTitleModDirMenuItem
|
||||||
//
|
//
|
||||||
|
@ -187,6 +197,7 @@ namespace Ryujinx.Ui.Widgets
|
||||||
Add(new SeparatorMenuItem());
|
Add(new SeparatorMenuItem());
|
||||||
Add(_manageTitleUpdatesMenuItem);
|
Add(_manageTitleUpdatesMenuItem);
|
||||||
Add(_manageDlcMenuItem);
|
Add(_manageDlcMenuItem);
|
||||||
|
Add(_manageCheatMenuItem);
|
||||||
Add(_openTitleModDirMenuItem);
|
Add(_openTitleModDirMenuItem);
|
||||||
Add(new SeparatorMenuItem());
|
Add(new SeparatorMenuItem());
|
||||||
Add(_manageCacheMenuItem);
|
Add(_manageCacheMenuItem);
|
||||||
|
|
|
@ -469,6 +469,11 @@ namespace Ryujinx.Ui.Widgets
|
||||||
new DlcWindow(_virtualFileSystem, _titleIdText, _titleName).Show();
|
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)
|
private void OpenTitleModDir_Clicked(object sender, EventArgs args)
|
||||||
{
|
{
|
||||||
string modsBasePath = _virtualFileSystem.ModLoader.GetModsBasePath();
|
string modsBasePath = _virtualFileSystem.ModLoader.GetModsBasePath();
|
||||||
|
|
155
Ryujinx/Ui/Windows/CheatWindow.cs
Normal file
155
Ryujinx/Ui/Windows/CheatWindow.cs
Normal file
|
@ -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<string> enabledCheats = new List<string>();
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
135
Ryujinx/Ui/Windows/CheatWindow.glade
Normal file
135
Ryujinx/Ui/Windows/CheatWindow.glade
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Generated with glade 3.21.0 -->
|
||||||
|
<interface>
|
||||||
|
<requires lib="gtk+" version="3.20"/>
|
||||||
|
<object class="GtkWindow" id="_cheatWindow">
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="title" translatable="yes">Ryujinx - Cheat Manager</property>
|
||||||
|
<property name="default_width">440</property>
|
||||||
|
<property name="default_height">550</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="MainBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox" id="CheatBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="orientation">vertical</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkLabel" id="_baseTitleInfoLabel">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_top">10</property>
|
||||||
|
<property name="margin_bottom">10</property>
|
||||||
|
<property name="label" translatable="yes">Available Cheats</property>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkScrolledWindow">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="margin_left">10</property>
|
||||||
|
<property name="margin_right">10</property>
|
||||||
|
<property name="shadow_type">in</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkViewport">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkTreeView" id="_cheatTreeView">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<child internal-child="selection">
|
||||||
|
<object class="GtkTreeSelection"/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButtonBox">
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">False</property>
|
||||||
|
<property name="margin_top">10</property>
|
||||||
|
<property name="margin_bottom">10</property>
|
||||||
|
<property name="layout_style">end</property>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="_saveButton">
|
||||||
|
<property name="label" translatable="yes">Save</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="margin_right">10</property>
|
||||||
|
<property name="margin_top">2</property>
|
||||||
|
<property name="margin_bottom">2</property>
|
||||||
|
<signal name="clicked" handler="SaveButton_Clicked" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">0</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="_cancelButton">
|
||||||
|
<property name="label" translatable="yes">Cancel</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can_focus">True</property>
|
||||||
|
<property name="receives_default">True</property>
|
||||||
|
<property name="margin_right">10</property>
|
||||||
|
<property name="margin_top">2</property>
|
||||||
|
<property name="margin_bottom">2</property>
|
||||||
|
<signal name="clicked" handler="CancelButton_Clicked" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">True</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">1</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</child>
|
||||||
|
<child type="titlebar">
|
||||||
|
<placeholder/>
|
||||||
|
</child>
|
||||||
|
</object>
|
||||||
|
</interface>
|
Reference in a new issue