tgstation-server 6.19.0
The /tg/station 13 server suite
Loading...
Searching...
No Matches
WindowsByondInstaller.cs
Go to the documentation of this file.
1using System;
2using System.Collections.Generic;
3using System.Text;
4using System.Threading;
5using System.Threading.Tasks;
6
7using Microsoft.Extensions.Logging;
8using Microsoft.Extensions.Options;
9
17
19{
24 {
28 const string ByondConfigDirectory = "byond/cfg";
29
33 const string ByondDreamDaemonConfigFilename = "daemon.txt";
34
38 const string ByondNoPromptTrustedMode = "trusted-check 0";
39
43 const string ByondDXDir = "byond/directx";
44
48 const string TgsFirewalledDDFile = "TGSFirewalledDD";
49
53 const string TrustedDmbFileName = "trusted.txt";
54
58 static readonly SemaphoreSlim UserFilesSemaphore = new(1, 1);
59
63 public static Version DDExeVersion => new(515, 1598);
64
66 protected override string DreamMakerName => "dm.exe";
67
69 protected override string PathToUserFolder { get; }
70
72 protected override string OSMarkerTemplate => "Windows";
73
78
83
87 readonly SemaphoreSlim semaphore;
88
93
105 IIOManager ioManager,
107 IOptionsMonitor<GeneralConfiguration> generalConfigurationOptions,
108 IOptions<SessionConfiguration> sessionConfigurationOptions,
109 ILogger<WindowsByondInstaller> logger)
110 : base(ioManager, logger, fileDownloader, generalConfigurationOptions)
111 {
112 this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
113 sessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions));
114
115 var useServiceSpecialTactics = Environment.Is64BitProcess && Environment.UserName == $"{Environment.MachineName}$";
116
117 var documentsDirectory = useServiceSpecialTactics
118 ? Environment.ExpandEnvironmentVariables("%SystemRoot%\\SysWOW64\\config\\systemprofile\\Documents")
119 : Environment.GetFolderPath(
120 Environment.SpecialFolder.MyDocuments,
121 Environment.SpecialFolderOption.DoNotVerify);
122
124 IOManager.ConcatPath(documentsDirectory, "BYOND"));
125
126 semaphore = new SemaphoreSlim(1);
127 installedDirectX = false;
128 }
129
131 public void Dispose() => semaphore.Dispose();
132
134 public override async ValueTask UpgradeInstallation(EngineVersion version, string path, CancellationToken cancellationToken)
135 {
136 CheckVersionValidity(version);
137 ArgumentNullException.ThrowIfNull(path);
138
139 if (GeneralConfigurationOptions.CurrentValue.SkipAddingByondFirewallException)
140 return;
141
142 if (version.Version < DDExeVersion)
143 return;
144
145 if (await IOManager.FileExists(IOManager.ConcatPath(path, TgsFirewalledDDFile), cancellationToken))
146 return;
147
148 Logger.LogInformation("BYOND Version {version} needs dd.exe added to firewall", version);
149 await AddDreamDaemonToFirewall(version, path, true, cancellationToken);
150 }
151
153 public override async ValueTask TrustDmbPath(EngineVersion version, string fullDmbPath, CancellationToken cancellationToken)
154 {
155 ArgumentNullException.ThrowIfNull(version);
156 ArgumentNullException.ThrowIfNull(fullDmbPath);
157
158 var byondDir = PathToUserFolder;
159 var cfgDir = IOManager.ConcatPath(
160 byondDir,
162 var trustedFilePath = IOManager.ConcatPath(
163 cfgDir,
165
166 Logger.LogDebug("Adding .dmb ({dmbPath}) to {trustedFilePath}", fullDmbPath, trustedFilePath);
167
168 using (await SemaphoreSlimContext.Lock(UserFilesSemaphore, cancellationToken))
169 {
170 string trustedFileText;
171 var filePreviouslyExisted = await IOManager.FileExists(trustedFilePath, cancellationToken);
172 if (filePreviouslyExisted)
173 {
174 var trustedFileBytes = await IOManager.ReadAllBytes(trustedFilePath, cancellationToken);
175 trustedFileText = Encoding.UTF8.GetString(trustedFileBytes);
176 trustedFileText = $"{trustedFileText.Trim()}{Environment.NewLine}";
177 }
178 else
179 trustedFileText = String.Empty;
180
181 if (trustedFileText.Contains(fullDmbPath, StringComparison.Ordinal))
182 return;
183
184 trustedFileText = $"{trustedFileText}{fullDmbPath}{Environment.NewLine}";
185
186 var newTrustedFileBytes = Encoding.UTF8.GetBytes(trustedFileText);
187
188 if (!filePreviouslyExisted)
189 await IOManager.CreateDirectory(cfgDir, cancellationToken);
190
191 await IOManager.WriteAllBytes(trustedFilePath, newTrustedFileBytes, cancellationToken);
192 }
193 }
194
196 protected override ValueTask InstallImpl(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
197 {
198 var noPromptTrustedTask = SetNoPromptTrusted(path, cancellationToken);
199 var installDirectXTask = InstallDirectX(path, cancellationToken);
200 var tasks = new List<ValueTask>(3)
201 {
202 noPromptTrustedTask,
203 installDirectXTask,
204 };
205
206 if (!GeneralConfigurationOptions.CurrentValue.SkipAddingByondFirewallException)
207 {
208 var firewallTask = AddDreamDaemonToFirewall(version, path, deploymentPipelineProcesses, cancellationToken);
209 tasks.Add(firewallTask);
210 }
211
212 return ValueTaskExtensions.WhenAll(tasks);
213 }
214
216 protected override string GetDreamDaemonName(Version byondVersion, out bool supportsCli)
217 {
218 supportsCli = byondVersion >= DDExeVersion && !sessionConfiguration.ForceUseDreamDaemonExe;
219 return supportsCli ? "dd.exe" : "dreamdaemon.exe";
220 }
221
223 protected override IEnumerable<string> AdditionalCacheCleanFilePaths(string configDirectory)
224 {
225 // Delete trusted.txt so it doesn't grow too large
226 var trustedFilePath =
228 configDirectory,
230
231 Logger.LogTrace("Deleting trusted .dmbs file {trustedFilePath}", trustedFilePath);
232 yield return trustedFilePath;
233 }
234
241 async ValueTask SetNoPromptTrusted(string path, CancellationToken cancellationToken)
242 {
243 var configPath = IOManager.ConcatPath(path, ByondConfigDirectory);
244 await IOManager.CreateDirectory(configPath, cancellationToken);
245
246 var configFilePath = IOManager.ConcatPath(configPath, ByondDreamDaemonConfigFilename);
247 Logger.LogTrace("Disabling trusted prompts in {configFilePath}...", configFilePath);
249 configFilePath,
250 Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode),
251 cancellationToken);
252 }
253
260 async ValueTask InstallDirectX(string path, CancellationToken cancellationToken)
261 {
262 using var lockContext = await SemaphoreSlimContext.Lock(semaphore, cancellationToken);
264 {
265 Logger.LogTrace("DirectX already installed.");
266 return;
267 }
268
269 Logger.LogTrace("Installing DirectX redistributable...");
270
271 // always install it, it's pretty fast and will do better redundancy checking than us
272 var rbdx = IOManager.ConcatPath(path, ByondDXDir);
273
274 try
275 {
276 // noShellExecute because we aren't doing runas shennanigans
277 await using var directXInstaller = await processExecutor.LaunchProcess(
278 IOManager.ConcatPath(rbdx, "DXSETUP.exe"),
279 rbdx,
280 "/silent",
281 cancellationToken,
282 noShellExecute: true);
283
284 int exitCode;
285 using (cancellationToken.Register(() => directXInstaller.Terminate()))
286 exitCode = (await directXInstaller.Lifetime).Value;
287 cancellationToken.ThrowIfCancellationRequested();
288
289 if (exitCode != 0)
290 throw new JobException(ErrorCode.ByondDirectXInstallFail, new JobException($"Invalid exit code: {exitCode}"));
291 installedDirectX = true;
292 }
293 catch (Exception e)
294 {
295 throw new JobException(ErrorCode.ByondDirectXInstallFail, e);
296 }
297 }
298
307 async ValueTask AddDreamDaemonToFirewall(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
308 {
309 var dreamDaemonName = GetDreamDaemonName(version.Version!, out var usesDDExe);
310
311 var dreamDaemonPath = IOManager.ResolvePath(
313 path,
315 dreamDaemonName));
316
317 int exitCode;
318 try
319 {
320 // I really wish we could add the instance name here but
321 // 1. It'd make IByondInstaller need to be transient per-instance and WindowsByondInstaller relys on being a singleton for its DX installer call
322 // 2. The instance could be renamed, so it'd have to be an unfriendly ID anyway.
323 var ruleName = $"TGS DreamDaemon {version}";
324
327 Logger,
328 ruleName,
329 dreamDaemonPath,
330 deploymentPipelineProcesses && sessionConfiguration.LowPriorityDeploymentProcesses,
331 cancellationToken);
332 }
333 catch (Exception ex)
334 {
335 throw new JobException(ErrorCode.EngineFirewallFail, ex);
336 }
337
338 if (exitCode != 0)
339 throw new JobException(ErrorCode.EngineFirewallFail, new JobException($"Invalid exit code: {exitCode}"));
340
341 if (usesDDExe)
344 Array.Empty<byte>(),
345 cancellationToken);
346 }
347 }
348}
Information about an engine installation.
Version? Version
The System.Version of the engine. Currently only valid when Engine is EngineType.Byond.
Extension methods for the ValueTask and ValueTask<TResult> classes.
static async ValueTask WhenAll(IEnumerable< ValueTask > tasks)
Fully await a given list of tasks .
Base implementation of IEngineInstaller for EngineType.Byond.
readonly IFileDownloader fileDownloader
The IFileDownloader for the ByondInstallerBase.
const string CfgDirectoryName
The path to the cfg directory.
const string ByondBinPath
The path to the BYOND bin folder.
IOptionsMonitor< GeneralConfiguration > GeneralConfigurationOptions
The GeneralConfiguration IOptionsMonitor<TOptions> for the ByondInstallerBase.
void CheckVersionValidity(EngineVersion version)
Check that a given version is of type EngineType.Byond.
IIOManager IOManager
Gets the IIOManager for the EngineInstallerBase.
ILogger< EngineInstallerBase > Logger
Gets the ILogger for the EngineInstallerBase.
const string ByondNoPromptTrustedMode
Setting to add to ByondDreamDaemonConfigFilename to suppress an invisible user prompt for running a t...
WindowsByondInstaller(IProcessExecutor processExecutor, IIOManager ioManager, IFileDownloader fileDownloader, IOptionsMonitor< GeneralConfiguration > generalConfigurationOptions, IOptions< SessionConfiguration > sessionConfigurationOptions, ILogger< WindowsByondInstaller > logger)
Initializes a new instance of the WindowsByondInstaller class.
override string GetDreamDaemonName(Version byondVersion, out bool supportsCli)
readonly IProcessExecutor processExecutor
The IProcessExecutor for the WindowsByondInstaller.
override ValueTask InstallImpl(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
const string ByondDreamDaemonConfigFilename
BYOND's DreamDaemon config file.
const string TgsFirewalledDDFile
The file TGS uses to determine if dd.exe has been firewalled.
async ValueTask AddDreamDaemonToFirewall(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
Attempt to add the DreamDaemon executable as an exception to the Windows firewall.
const string ByondConfigDirectory
Directory to byond installation configuration.
const string ByondDXDir
The directory that contains the BYOND directx redistributable.
static Version DDExeVersion
The first version of BYOND to ship with dd.exe on the Windows build.
const string TrustedDmbFileName
The name of the list of trusted .dmb files in the user's BYOND cfg directory.
override IEnumerable< string > AdditionalCacheCleanFilePaths(string configDirectory)
List off additional file paths in the configDirectory to delete.An IEnumerable<T> of paths in config...
override async ValueTask UpgradeInstallation(EngineVersion version, string path, CancellationToken cancellationToken)
Does actions necessary to get upgrade a version installed by a previous version of TGS....
async ValueTask SetNoPromptTrusted(string path, CancellationToken cancellationToken)
Creates the BYOND cfg file that prevents the trusted mode dialog from appearing when launching DreamD...
async ValueTask InstallDirectX(string path, CancellationToken cancellationToken)
Attempt to install the DirectX redistributable included with BYOND.
readonly SessionConfiguration sessionConfiguration
The SessionConfiguration for the WindowsByondInstaller.
readonly SemaphoreSlim semaphore
The SemaphoreSlim for the WindowsByondInstaller.
override async ValueTask TrustDmbPath(EngineVersion version, string fullDmbPath, CancellationToken cancellationToken)
Add a given fullDmbPath to the trusted DMBs list in BYOND's config.A ValueTask representing the runn...
static readonly SemaphoreSlim UserFilesSemaphore
SemaphoreSlim for writing to files in the user's BYOND directory.
Configuration options for the game sessions.
bool ForceUseDreamDaemonExe
If set dd.exe will not be used on Windows systems in versions where it is present....
bool LowPriorityDeploymentProcesses
If the deployment DreamMaker and DreamDaemon instances are set to be below normal priority processes.
Operation exceptions thrown from the context of a Models.Job.
Helper class for interacting with the Windows Firewall.
static async ValueTask< int > AddFirewallException(IProcessExecutor processExecutor, ILogger logger, string exceptionName, string exePath, bool lowPriority, CancellationToken cancellationToken)
Add an executable exception to the Windows firewall.
static async ValueTask< SemaphoreSlimContext > Lock(SemaphoreSlim semaphore, CancellationToken cancellationToken, ILogger? logger=null)
Asyncronously locks a semaphore .
Interface for using filesystems.
Definition IIOManager.cs:14
string ResolvePath()
Retrieve the full path of the current working directory.
ValueTask< byte[]> ReadAllBytes(string path, CancellationToken cancellationToken)
Returns all the contents of a file at path as a byte array.
string ConcatPath(params string[] paths)
Combines an array of strings into a path.
Task CreateDirectory(string path, CancellationToken cancellationToken)
Create a directory at path .
ValueTask WriteAllBytes(string path, byte[] contents, CancellationToken cancellationToken)
Writes some contents to a file at path overwriting previous content.
Task< bool > FileExists(string path, CancellationToken cancellationToken)
Check that the file at path exists.
ValueTask< IProcess > LaunchProcess(string fileName, string workingDirectory, string arguments, CancellationToken cancellationToken, IReadOnlyDictionary< string, string >? environment=null, string? fileRedirect=null, bool readStandardHandles=false, bool noShellExecute=false)
Launch a IProcess.
ErrorCode
Types of Response.ErrorMessageResponses that the API may return.
Definition ErrorCode.cs:12