mirror of
https://github.com/gwdevhub/Daybreak.git
synced 2026-07-15 15:19:57 +00:00
@@ -28,9 +28,10 @@ internal sealed class ApplicationLauncher(
|
||||
INotificationService notificationService,
|
||||
IOptionsMonitor<LauncherOptions> launcherOptions,
|
||||
IModsManager modsManager,
|
||||
IPrivilegeManager privilegeManager,
|
||||
IGuildWarsReadyChecker guildWarsReadyChecker,
|
||||
IGuildWarsProcessFinder guildWarsProcessFinder,
|
||||
IDaybreakRestartingService daybreakRestartingService,
|
||||
IPrivilegeManager privilegeManager,
|
||||
ILogger<ApplicationLauncher> logger
|
||||
) : IApplicationLauncher
|
||||
{
|
||||
@@ -48,11 +49,15 @@ internal sealed class ApplicationLauncher(
|
||||
launcherOptions.ThrowIfNull();
|
||||
private readonly IModsManager modsManager = modsManager.ThrowIfNull();
|
||||
private readonly ILogger<ApplicationLauncher> logger = logger.ThrowIfNull();
|
||||
private readonly IPrivilegeManager privilegeManager = privilegeManager.ThrowIfNull();
|
||||
private readonly IGuildWarsReadyChecker guildWarsReadyChecker =
|
||||
guildWarsReadyChecker.ThrowIfNull();
|
||||
private readonly IGuildWarsProcessFinder guildWarsProcessFinder =
|
||||
guildWarsProcessFinder.ThrowIfNull();
|
||||
private readonly IDaybreakRestartingService daybreakRestartingService =
|
||||
daybreakRestartingService.ThrowIfNull();
|
||||
|
||||
private readonly IPrivilegeManager privilegeManager =
|
||||
privilegeManager.ThrowIfNull();
|
||||
|
||||
public async Task<GuildWarsApplicationLaunchContext?> LaunchGuildwars(
|
||||
LaunchConfigurationWithCredentials launchConfigurationWithCredentials,
|
||||
@@ -83,76 +88,19 @@ internal sealed class ApplicationLauncher(
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: Needs to be reworked to be cross-platform
|
||||
public void RestartDaybreak()
|
||||
{
|
||||
if (this.privilegeManager.AdminPrivileges)
|
||||
{
|
||||
this.RestartDaybreakAsAdmin();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.RestartDaybreakAsNormalUser();
|
||||
}
|
||||
this.daybreakRestartingService.RestartDaybreak();
|
||||
}
|
||||
|
||||
// TODO: Needs to be reworked to be cross-platform
|
||||
public void RestartDaybreakAsAdmin()
|
||||
{
|
||||
this.logger.LogInformation("Restarting daybreak with admin rights");
|
||||
var processName = Process.GetCurrentProcess()?.MainModule?.FileName;
|
||||
if (processName!.IsNullOrWhiteSpace() || File.Exists(processName) is false)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to find executable. Aborting restart");
|
||||
}
|
||||
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
Verb = "runas",
|
||||
WorkingDirectory = Environment.CurrentDirectory,
|
||||
UseShellExecute = true,
|
||||
CreateNoWindow = true,
|
||||
FileName = "cmd.exe",
|
||||
Arguments =
|
||||
$"/c cd /d \"{Environment.CurrentDirectory}\" && timeout /t 1 /nobreak && start \"\" \"{processName}\" && exit",
|
||||
},
|
||||
};
|
||||
if (process.Start() is false)
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to start {processName} as admin");
|
||||
}
|
||||
|
||||
this.photinoWindow.Close();
|
||||
this.daybreakRestartingService.RestartDaybreakAsAdmin();
|
||||
}
|
||||
|
||||
// TODO: Needs to be reworked to be cross-platform
|
||||
public void RestartDaybreakAsNormalUser()
|
||||
{
|
||||
this.logger.LogInformation("Restarting daybreak as normal user");
|
||||
var processName = Process.GetCurrentProcess()?.MainModule?.FileName;
|
||||
if (processName!.IsNullOrWhiteSpace() || File.Exists(processName) is false)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to find executable. Aborting restart");
|
||||
}
|
||||
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
WorkingDirectory = Environment.CurrentDirectory,
|
||||
FileName = processName,
|
||||
UseShellExecute = true,
|
||||
CreateNoWindow = true,
|
||||
},
|
||||
};
|
||||
if (process.Start() is false)
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to start {processName} as normal user");
|
||||
}
|
||||
|
||||
this.photinoWindow.Close();
|
||||
this.daybreakRestartingService.RestartDaybreakAsNormalUser();
|
||||
}
|
||||
|
||||
private async Task<Process?> LaunchGuildwarsProcess(
|
||||
|
||||
@@ -4,8 +4,8 @@ using System.Net;
|
||||
using System.Text;
|
||||
|
||||
const string LatestUrl = "https://github.com/gwdevhub/Daybreak/releases/latest";
|
||||
var daybreakExecutable = OperatingSystem.IsWindows() ? "Daybreak.exe" : "Daybreak.Linux";
|
||||
var daybreakProcessName = OperatingSystem.IsWindows() ? "Daybreak" : "Daybreak.Linux";
|
||||
var daybreakExecutable = OperatingSystem.IsWindows() ? "Daybreak.exe" : "Daybreak";
|
||||
var daybreakProcessName = "Daybreak";
|
||||
var releaseAssetSuffix = OperatingSystem.IsWindows() ? "" : "-linux";
|
||||
|
||||
static void RenderProgressBar(int currentStep, int totalSteps, int barSize)
|
||||
|
||||
@@ -61,6 +61,7 @@ public sealed class LinuxPlatformConfiguration : PluginConfigurationBase
|
||||
services.AddHostedSingleton<IMDomainRegistrar, PortScanningDomainRegistrar>();
|
||||
services.AddSingleton<IWindowManipulationService, WindowManipulationService>();
|
||||
services.AddSingleton<ICrashDumpService, CrashDumpService>();
|
||||
services.AddSingleton<IDaybreakRestartingService, DaybreakRestartingService>();
|
||||
}
|
||||
|
||||
public override void RegisterStartupActions(IStartupActionProducer startupActionProducer)
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
using Daybreak.Shared.Services.ApplicationLauncher;
|
||||
using Daybreak.Shared.Services.Privilege;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Photino.NET;
|
||||
using System.Core.Extensions;
|
||||
using System.Diagnostics;
|
||||
using System.Extensions;
|
||||
using System.Extensions.Core;
|
||||
|
||||
namespace Daybreak.Linux.Services.ApplicationLauncher;
|
||||
|
||||
/// <summary>
|
||||
/// Linux-specific implementation for restarting the Daybreak application.
|
||||
/// Uses bash for process spawning and pkexec/sudo for privilege elevation.
|
||||
/// </summary>
|
||||
internal sealed class DaybreakRestartingService(
|
||||
PhotinoWindow photinoWindow,
|
||||
IPrivilegeManager privilegeManager,
|
||||
ILogger<DaybreakRestartingService> logger
|
||||
) : IDaybreakRestartingService
|
||||
{
|
||||
private readonly PhotinoWindow photinoWindow = photinoWindow.ThrowIfNull();
|
||||
private readonly IPrivilegeManager privilegeManager = privilegeManager.ThrowIfNull();
|
||||
private readonly ILogger<DaybreakRestartingService> logger = logger.ThrowIfNull();
|
||||
|
||||
public void RestartDaybreak()
|
||||
{
|
||||
if (this.privilegeManager.AdminPrivileges)
|
||||
{
|
||||
this.RestartDaybreakAsAdmin();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.RestartDaybreakAsNormalUser();
|
||||
}
|
||||
}
|
||||
|
||||
public void RestartDaybreakAsAdmin()
|
||||
{
|
||||
this.logger.LogInformation("Restarting daybreak with elevated privileges");
|
||||
var processName = Process.GetCurrentProcess()?.MainModule?.FileName;
|
||||
if (processName!.IsNullOrWhiteSpace() || File.Exists(processName) is false)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to find executable. Aborting restart");
|
||||
}
|
||||
|
||||
// On Linux, use pkexec for graphical privilege elevation
|
||||
// Fall back to sudo if pkexec is not available
|
||||
var elevationCommand = GetElevationCommand();
|
||||
if (elevationCommand is null)
|
||||
{
|
||||
this.logger.LogWarning("No elevation command (pkexec/sudo) found. Restarting without elevation");
|
||||
this.RestartDaybreakAsNormalUser();
|
||||
return;
|
||||
}
|
||||
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
WorkingDirectory = Environment.CurrentDirectory,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
FileName = "/bin/bash",
|
||||
Arguments = $"-c \"sleep 1 && {elevationCommand} \\\"{processName}\\\" &\"",
|
||||
},
|
||||
};
|
||||
if (process.Start() is false)
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to start {processName} with elevated privileges");
|
||||
}
|
||||
|
||||
this.photinoWindow.Close();
|
||||
}
|
||||
|
||||
public void RestartDaybreakAsNormalUser()
|
||||
{
|
||||
this.logger.LogInformation("Restarting daybreak as normal user");
|
||||
var processName = Process.GetCurrentProcess()?.MainModule?.FileName;
|
||||
if (processName!.IsNullOrWhiteSpace() || File.Exists(processName) is false)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to find executable. Aborting restart");
|
||||
}
|
||||
|
||||
// Use nohup to ensure the process survives parent exit, and disown to detach from terminal
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
WorkingDirectory = Environment.CurrentDirectory,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true,
|
||||
FileName = "/bin/bash",
|
||||
Arguments = $"-c \"sleep 1 && nohup \\\"{processName}\\\" > /dev/null 2>&1 &\"",
|
||||
},
|
||||
};
|
||||
if (process.Start() is false)
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to start {processName} as normal user");
|
||||
}
|
||||
|
||||
this.photinoWindow.Close();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the appropriate elevation command for the system.
|
||||
/// Prefers pkexec (graphical) over sudo (terminal-based).
|
||||
/// </summary>
|
||||
private static string? GetElevationCommand()
|
||||
{
|
||||
// Check for pkexec first (graphical polkit elevation)
|
||||
if (CommandExists("pkexec"))
|
||||
{
|
||||
return "pkexec";
|
||||
}
|
||||
|
||||
// Fall back to sudo
|
||||
if (CommandExists("sudo"))
|
||||
{
|
||||
return "sudo";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a command exists in the system PATH.
|
||||
/// </summary>
|
||||
private static bool CommandExists(string command)
|
||||
{
|
||||
try
|
||||
{
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
FileName = "/bin/bash",
|
||||
Arguments = $"-c \"command -v {command}\"",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true,
|
||||
CreateNoWindow = true,
|
||||
},
|
||||
};
|
||||
process.Start();
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
return process.ExitCode == 0 && !string.IsNullOrWhiteSpace(output);
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
namespace Daybreak.Shared.Services.ApplicationLauncher;
|
||||
|
||||
/// <summary>
|
||||
/// Platform-specific service for restarting the Daybreak application.
|
||||
/// </summary>
|
||||
public interface IDaybreakRestartingService
|
||||
{
|
||||
/// <summary>
|
||||
/// Restarts Daybreak, choosing the appropriate privilege level based on current state.
|
||||
/// </summary>
|
||||
void RestartDaybreak();
|
||||
|
||||
/// <summary>
|
||||
/// Restarts Daybreak with elevated privileges.
|
||||
/// </summary>
|
||||
void RestartDaybreakAsAdmin();
|
||||
|
||||
/// <summary>
|
||||
/// Restarts Daybreak as a normal user (without elevation).
|
||||
/// </summary>
|
||||
void RestartDaybreakAsNormalUser();
|
||||
}
|
||||
@@ -80,6 +80,7 @@ public sealed class WindowsPlatformConfiguration : PluginConfigurationBase
|
||||
services.AddHostedSingleton<IMDomainRegistrar, MDomainRegistrar>();
|
||||
services.AddSingleton<IWindowManipulationService, WindowManipulationService>();
|
||||
services.AddSingleton<ICrashDumpService, CrashDumpService>();
|
||||
services.AddSingleton<IDaybreakRestartingService, DaybreakRestartingService>();
|
||||
}
|
||||
|
||||
public override void RegisterMods(IModsProducer modsProducer)
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
using Daybreak.Shared.Services.ApplicationLauncher;
|
||||
using Daybreak.Shared.Services.Privilege;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Photino.NET;
|
||||
using System.Core.Extensions;
|
||||
using System.Diagnostics;
|
||||
using System.Extensions;
|
||||
using System.Extensions.Core;
|
||||
|
||||
namespace Daybreak.Windows.Services.ApplicationLauncher;
|
||||
|
||||
/// <summary>
|
||||
/// Windows-specific implementation for restarting the Daybreak application.
|
||||
/// Uses cmd.exe with 'runas' verb for admin elevation.
|
||||
/// </summary>
|
||||
internal sealed class DaybreakRestartingService(
|
||||
PhotinoWindow photinoWindow,
|
||||
IPrivilegeManager privilegeManager,
|
||||
ILogger<DaybreakRestartingService> logger
|
||||
) : IDaybreakRestartingService
|
||||
{
|
||||
private readonly PhotinoWindow photinoWindow = photinoWindow.ThrowIfNull();
|
||||
private readonly IPrivilegeManager privilegeManager = privilegeManager.ThrowIfNull();
|
||||
private readonly ILogger<DaybreakRestartingService> logger = logger.ThrowIfNull();
|
||||
|
||||
public void RestartDaybreak()
|
||||
{
|
||||
if (this.privilegeManager.AdminPrivileges)
|
||||
{
|
||||
this.RestartDaybreakAsAdmin();
|
||||
}
|
||||
else
|
||||
{
|
||||
this.RestartDaybreakAsNormalUser();
|
||||
}
|
||||
}
|
||||
|
||||
public void RestartDaybreakAsAdmin()
|
||||
{
|
||||
this.logger.LogInformation("Restarting daybreak with admin rights");
|
||||
var processName = Process.GetCurrentProcess()?.MainModule?.FileName;
|
||||
if (processName!.IsNullOrWhiteSpace() || File.Exists(processName) is false)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to find executable. Aborting restart");
|
||||
}
|
||||
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
Verb = "runas",
|
||||
WorkingDirectory = Environment.CurrentDirectory,
|
||||
UseShellExecute = true,
|
||||
CreateNoWindow = true,
|
||||
FileName = "cmd.exe",
|
||||
Arguments =
|
||||
$"/c cd /d \"{Environment.CurrentDirectory}\" && timeout /t 1 /nobreak && start \"\" \"{processName}\" && exit",
|
||||
},
|
||||
};
|
||||
if (process.Start() is false)
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to start {processName} as admin");
|
||||
}
|
||||
|
||||
this.photinoWindow.Close();
|
||||
}
|
||||
|
||||
public void RestartDaybreakAsNormalUser()
|
||||
{
|
||||
this.logger.LogInformation("Restarting daybreak as normal user");
|
||||
var processName = Process.GetCurrentProcess()?.MainModule?.FileName;
|
||||
if (processName!.IsNullOrWhiteSpace() || File.Exists(processName) is false)
|
||||
{
|
||||
throw new InvalidOperationException("Unable to find executable. Aborting restart");
|
||||
}
|
||||
|
||||
var process = new Process()
|
||||
{
|
||||
StartInfo = new()
|
||||
{
|
||||
WorkingDirectory = Environment.CurrentDirectory,
|
||||
FileName = processName,
|
||||
UseShellExecute = true,
|
||||
CreateNoWindow = true,
|
||||
},
|
||||
};
|
||||
if (process.Start() is false)
|
||||
{
|
||||
throw new InvalidOperationException($"Unable to start {processName} as normal user");
|
||||
}
|
||||
|
||||
this.photinoWindow.Close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user