/ src / Ryujinx.HLE / HOS / TamperMachine.cs
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  }