/src/XIVLauncher/Addon/Implementations/GenericAddon.cs

https://github.com/goatcorp/FFXIVQuickLauncher · C# · 211 lines · 169 code · 36 blank · 6 comment · 23 complexity · 6c7b7649cd00b49849768989cda6c237 MD5 · raw file

  1. using Serilog;
  2. using System;
  3. using System.ComponentModel;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Linq;
  7. using XIVLauncher.Settings;
  8. namespace XIVLauncher.Addon
  9. {
  10. public class GenericAddon : IRunnableAddon, INotifyAddonAfterClose
  11. {
  12. private Process _addonProcess;
  13. private Process _gameProcess;
  14. void IAddon.Setup(Process gameProcess, ILauncherSettingsV3 setting)
  15. {
  16. _gameProcess = gameProcess;
  17. }
  18. public void Run() =>
  19. Run(false);
  20. private void Run(bool gameClosed)
  21. {
  22. if (string.IsNullOrEmpty(Path))
  23. {
  24. Log.Error("Generic addon path was null.");
  25. return;
  26. }
  27. if (RunOnClose && !gameClosed)
  28. return; // This Addon only runs when the game is closed.
  29. try
  30. {
  31. var ext = System.IO.Path.GetExtension(Path).ToLower();
  32. switch (ext)
  33. {
  34. case ".ps1":
  35. RunPowershell();
  36. break;
  37. case ".bat":
  38. RunBatch();
  39. break;
  40. default:
  41. RunApp();
  42. break;
  43. }
  44. Log.Information("Launched addon {0}.", System.IO.Path.GetFileNameWithoutExtension(Path));
  45. }
  46. catch (Exception e)
  47. {
  48. Log.Error(e, "Could not launch generic addon.");
  49. }
  50. }
  51. public void GameClosed()
  52. {
  53. if (RunOnClose)
  54. {
  55. Run(true);
  56. }
  57. if (!RunAsAdmin)
  58. {
  59. try
  60. {
  61. if (_addonProcess == null)
  62. return;
  63. if (_addonProcess.Handle == IntPtr.Zero)
  64. return;
  65. if (!_addonProcess.HasExited && KillAfterClose)
  66. {
  67. if (!_addonProcess.CloseMainWindow() || !_addonProcess.WaitForExit(1000))
  68. _addonProcess.Kill();
  69. _addonProcess.Close();
  70. }
  71. }
  72. catch (Exception ex)
  73. {
  74. Log.Information(ex, "Could not kill addon process.");
  75. }
  76. }
  77. }
  78. private void RunApp()
  79. {
  80. // If there already is a process like this running - we don't need to spawn another one.
  81. if (Process.GetProcessesByName(System.IO.Path.GetFileNameWithoutExtension(Path)).Any())
  82. {
  83. Log.Information("Addon {0} is already running.", Name);
  84. return;
  85. }
  86. _addonProcess = new Process
  87. {
  88. StartInfo =
  89. {
  90. FileName = Path,
  91. Arguments = CommandLine,
  92. WorkingDirectory = System.IO.Path.GetDirectoryName(Path),
  93. },
  94. };
  95. if (RunAsAdmin)
  96. // Vista or higher check
  97. // https://stackoverflow.com/a/2532775
  98. if (Environment.OSVersion.Version.Major >= 6) _addonProcess.StartInfo.Verb = "runas";
  99. _addonProcess.StartInfo.WindowStyle = ProcessWindowStyle.Minimized;
  100. _addonProcess.Start();
  101. }
  102. private void RunPowershell()
  103. {
  104. var ps = new ProcessStartInfo
  105. {
  106. FileName = Powershell,
  107. WorkingDirectory = System.IO.Path.GetDirectoryName(Path),
  108. Arguments = $@"-File ""{Path}"" {CommandLine}",
  109. UseShellExecute = false,
  110. };
  111. RunScript(ps);
  112. }
  113. private void RunBatch()
  114. {
  115. var ps = new ProcessStartInfo
  116. {
  117. FileName = Environment.GetEnvironmentVariable("ComSpec"),
  118. WorkingDirectory = System.IO.Path.GetDirectoryName(Path),
  119. Arguments = $@"/C ""{Path}"" {CommandLine}",
  120. UseShellExecute = false,
  121. };
  122. RunScript(ps);
  123. }
  124. private void RunScript(ProcessStartInfo ps)
  125. {
  126. ps.WindowStyle = ProcessWindowStyle.Hidden;
  127. ps.CreateNoWindow = true;
  128. if (RunAsAdmin)
  129. // Vista or higher check
  130. // https://stackoverflow.com/a/2532775
  131. if (Environment.OSVersion.Version.Major >= 6) ps.Verb = "runas";
  132. try
  133. {
  134. _addonProcess = Process.Start(ps);
  135. Log.Information("Launched addon {0}.", System.IO.Path.GetFileNameWithoutExtension(Path));
  136. }
  137. catch (Win32Exception exc)
  138. {
  139. // If the user didn't cause this manually by dismissing the UAC prompt, we throw it
  140. if ((uint)exc.HResult != 0x80004005)
  141. throw;
  142. }
  143. }
  144. public string Name =>
  145. string.IsNullOrEmpty(Path)
  146. ? "Invalid addon"
  147. : $"Launch{(IsApp ? " EXE" : string.Empty)} : {System.IO.Path.GetFileNameWithoutExtension(Path)}";
  148. private bool IsApp =>
  149. !string.IsNullOrEmpty(Path) &&
  150. System.IO.Path.GetExtension(Path).ToLower() == ".exe";
  151. public string Path;
  152. public string CommandLine;
  153. public bool RunAsAdmin;
  154. public bool RunOnClose;
  155. public bool KillAfterClose;
  156. private static readonly Lazy<string> LazyPowershell = new(GetPowershell);
  157. private static string Powershell => LazyPowershell.Value;
  158. private static string GetPowershell()
  159. {
  160. var result = "powershell.exe";
  161. var path = Environment.GetEnvironmentVariable("Path");
  162. var values = path?.Split(';') ?? Array.Empty<string>();
  163. foreach (var dir in values)
  164. {
  165. var powershell = System.IO.Path.Combine(dir, "pwsh.exe");
  166. if (File.Exists(powershell))
  167. {
  168. result = powershell;
  169. break;
  170. }
  171. }
  172. return result;
  173. }
  174. }
  175. }