TamperMachine.cs
1 using Ryujinx.Common.Logging; 2 using Ryujinx.HLE.HOS.Kernel; 3 using Ryujinx.HLE.HOS.Kernel.Process; 4 using Ryujinx.HLE.HOS.Services.Hid; 5 using Ryujinx.HLE.HOS.Tamper; 6 using System; 7 using System.Collections.Concurrent; 8 using System.Collections.Generic; 9 using System.Threading; 10 11 namespace Ryujinx.HLE.HOS 12 { 13 public class TamperMachine 14 { 15 // Atmosphere specifies a delay of 83 milliseconds between the execution of the last 16 // cheat and the re-execution of the first one. 17 private const int TamperMachineSleepMs = 1000 / 12; 18 19 private Thread _tamperThread = null; 20 private readonly ConcurrentQueue<ITamperProgram> _programs = new(); 21 private long _pressedKeys = 0; 22 private readonly Dictionary<string, ITamperProgram> _programDictionary = new(); 23 24 private void Activate() 25 { 26 if (_tamperThread == null || !_tamperThread.IsAlive) 27 { 28 _tamperThread = new Thread(this.TamperRunner) 29 { 30 Name = "HLE.TamperMachine", 31 }; 32 _tamperThread.Start(); 33 } 34 } 35 36 internal void InstallAtmosphereCheat(string name, string buildId, IEnumerable<string> rawInstructions, ProcessTamperInfo info, ulong exeAddress) 37 { 38 if (!CanInstallOnPid(info.Process.Pid)) 39 { 40 return; 41 } 42 43 ITamperedProcess tamperedProcess = new TamperedKProcess(info.Process); 44 AtmosphereCompiler compiler = new(exeAddress, info.HeapAddress, info.AliasAddress, info.AslrAddress, tamperedProcess); 45 ITamperProgram program = compiler.Compile(name, rawInstructions); 46 47 if (program != null) 48 { 49 program.TampersCodeMemory = false; 50 51 _programs.Enqueue(program); 52 _programDictionary.TryAdd($"{buildId}-{name}", program); 53 } 54 55 Activate(); 56 } 57 58 private static bool CanInstallOnPid(ulong pid) 59 { 60 // Do not allow tampering of kernel processes. 61 if (pid < KernelConstants.InitialProcessId) 62 { 63 Logger.Warning?.Print(LogClass.TamperMachine, $"Refusing to tamper kernel process {pid}"); 64 65 return false; 66 } 67 68 return true; 69 } 70 71 public void EnableCheats(string[] enabledCheats) 72 { 73 foreach (var program in _programDictionary.Values) 74 { 75 program.IsEnabled = false; 76 } 77 78 foreach (var cheat in enabledCheats) 79 { 80 if (_programDictionary.TryGetValue(cheat, out var program)) 81 { 82 program.IsEnabled = true; 83 } 84 } 85 } 86 87 private static bool IsProcessValid(ITamperedProcess process) 88 { 89 return process.State != ProcessState.Crashed && process.State != ProcessState.Exiting && process.State != ProcessState.Exited; 90 } 91 92 private void TamperRunner() 93 { 94 Logger.Info?.Print(LogClass.TamperMachine, "TamperMachine thread running"); 95 96 int sleepCounter = 0; 97 98 while (true) 99 { 100 // Sleep to not consume too much CPU. 101 if (sleepCounter == 0) 102 { 103 sleepCounter = _programs.Count; 104 Thread.Sleep(TamperMachineSleepMs); 105 } 106 else 107 { 108 sleepCounter--; 109 } 110 111 if (!AdvanceTamperingsQueue()) 112 { 113 // No more work to be done. 114 115 Logger.Info?.Print(LogClass.TamperMachine, "TamperMachine thread exiting"); 116 117 return; 118 } 119 } 120 } 121 122 private bool AdvanceTamperingsQueue() 123 { 124 if (!_programs.TryDequeue(out ITamperProgram program)) 125 { 126 // No more programs in the queue. 127 _programDictionary.Clear(); 128 129 return false; 130 } 131 132 // Check if the process is still suitable for running the tamper program. 133 if (!IsProcessValid(program.Process)) 134 { 135 // Exit without re-enqueuing the program because the process is no longer valid. 136 return true; 137 } 138 139 // Re-enqueue the tampering program because the process is still valid. 140 _programs.Enqueue(program); 141 142 Logger.Debug?.Print(LogClass.TamperMachine, $"Running tampering program {program.Name}"); 143 144 try 145 { 146 ControllerKeys pressedKeys = (ControllerKeys)Volatile.Read(ref _pressedKeys); 147 program.Process.TamperedCodeMemory = false; 148 program.Execute(pressedKeys); 149 150 // Detect the first attempt to tamper memory and log it. 151 if (!program.TampersCodeMemory && program.Process.TamperedCodeMemory) 152 { 153 program.TampersCodeMemory = true; 154 155 Logger.Warning?.Print(LogClass.TamperMachine, $"Tampering program {program.Name} modifies code memory so it may not work properly"); 156 } 157 } 158 catch (Exception ex) 159 { 160 Logger.Debug?.Print(LogClass.TamperMachine, $"The tampering program {program.Name} crashed, this can happen while the game is starting"); 161 162 if (!string.IsNullOrEmpty(ex.Message)) 163 { 164 Logger.Debug?.Print(LogClass.TamperMachine, ex.Message); 165 } 166 } 167 168 return true; 169 } 170 171 public void UpdateInput(List<GamepadInput> gamepadInputs) 172 { 173 // Look for the input of the player one or the handheld. 174 foreach (GamepadInput input in gamepadInputs) 175 { 176 if (input.PlayerId == PlayerIndex.Player1 || input.PlayerId == PlayerIndex.Handheld) 177 { 178 Volatile.Write(ref _pressedKeys, (long)input.Buttons); 179 180 return; 181 } 182 } 183 184 // Clear the input because player one is not conected. 185 Volatile.Write(ref _pressedKeys, 0); 186 } 187 } 188 }