tgstation-server 6.12.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 ByondRevisionsUrlTemplate => "https://www.byond.com/download/build/{0}/{0}.{1}_byond.zip";
73
78
83
88
92 readonly SemaphoreSlim semaphore;
93
98
110 IIOManager ioManager,
112 IOptions<GeneralConfiguration> generalConfigurationOptions,
113 IOptions<SessionConfiguration> sessionConfigurationOptions,
114 ILogger<WindowsByondInstaller> logger)
115 : base(ioManager, logger, fileDownloader)
116 {
117 this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
118 generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
119 sessionConfiguration = sessionConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(sessionConfigurationOptions));
120
121 var useServiceSpecialTactics = Environment.Is64BitProcess && Environment.UserName == $"{Environment.MachineName}$";
122
123 var documentsDirectory = useServiceSpecialTactics
124 ? Environment.ExpandEnvironmentVariables("%SystemRoot%\\SysWOW64\\config\\systemprofile\\Documents")
125 : Environment.GetFolderPath(
126 Environment.SpecialFolder.MyDocuments,
127 Environment.SpecialFolderOption.DoNotVerify);
128
130 IOManager.ConcatPath(documentsDirectory, "BYOND"));
131
132 semaphore = new SemaphoreSlim(1);
133 installedDirectX = false;
134 }
135
137 public void Dispose() => semaphore.Dispose();
138
140 public override ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
141 {
142 CheckVersionValidity(version);
143 ArgumentNullException.ThrowIfNull(path);
144
145 var noPromptTrustedTask = SetNoPromptTrusted(path, cancellationToken);
146 var installDirectXTask = InstallDirectX(path, cancellationToken);
147 var tasks = new List<ValueTask>(3)
148 {
149 noPromptTrustedTask,
150 installDirectXTask,
151 };
152
154 {
155 var firewallTask = AddDreamDaemonToFirewall(version, path, deploymentPipelineProcesses, cancellationToken);
156 tasks.Add(firewallTask);
157 }
158
159 return ValueTaskExtensions.WhenAll(tasks);
160 }
161
163 public override async ValueTask UpgradeInstallation(EngineVersion version, string path, CancellationToken cancellationToken)
164 {
165 CheckVersionValidity(version);
166 ArgumentNullException.ThrowIfNull(path);
167
169 return;
170
171 if (version.Version < DDExeVersion)
172 return;
173
174 if (await IOManager.FileExists(IOManager.ConcatPath(path, TgsFirewalledDDFile), cancellationToken))
175 return;
176
177 Logger.LogInformation("BYOND Version {version} needs dd.exe added to firewall", version);
178 await AddDreamDaemonToFirewall(version, path, true, cancellationToken);
179 }
180
182 public override async ValueTask TrustDmbPath(EngineVersion version, string fullDmbPath, CancellationToken cancellationToken)
183 {
184 ArgumentNullException.ThrowIfNull(version);
185 ArgumentNullException.ThrowIfNull(fullDmbPath);
186
187 var byondDir = PathToUserFolder;
188 var cfgDir = IOManager.ConcatPath(
189 byondDir,
191 var trustedFilePath = IOManager.ConcatPath(
192 cfgDir,
194
195 Logger.LogDebug("Adding .dmb ({dmbPath}) to {trustedFilePath}", fullDmbPath, trustedFilePath);
196
197 using (await SemaphoreSlimContext.Lock(UserFilesSemaphore, cancellationToken))
198 {
199 string trustedFileText;
200 var filePreviouslyExisted = await IOManager.FileExists(trustedFilePath, cancellationToken);
201 if (filePreviouslyExisted)
202 {
203 var trustedFileBytes = await IOManager.ReadAllBytes(trustedFilePath, cancellationToken);
204 trustedFileText = Encoding.UTF8.GetString(trustedFileBytes);
205 trustedFileText = $"{trustedFileText.Trim()}{Environment.NewLine}";
206 }
207 else
208 trustedFileText = String.Empty;
209
210 if (trustedFileText.Contains(fullDmbPath, StringComparison.Ordinal))
211 return;
212
213 trustedFileText = $"{trustedFileText}{fullDmbPath}{Environment.NewLine}";
214
215 var newTrustedFileBytes = Encoding.UTF8.GetBytes(trustedFileText);
216
217 if (!filePreviouslyExisted)
218 await IOManager.CreateDirectory(cfgDir, cancellationToken);
219
220 await IOManager.WriteAllBytes(trustedFilePath, newTrustedFileBytes, cancellationToken);
221 }
222 }
223
225 protected override string GetDreamDaemonName(Version byondVersion, out bool supportsCli)
226 {
227 supportsCli = byondVersion >= DDExeVersion && !sessionConfiguration.ForceUseDreamDaemonExe;
228 return supportsCli ? "dd.exe" : "dreamdaemon.exe";
229 }
230
232 protected override IEnumerable<string> AdditionalCacheCleanFilePaths(string configDirectory)
233 {
234 // Delete trusted.txt so it doesn't grow too large
235 var trustedFilePath =
237 configDirectory,
239
240 Logger.LogTrace("Deleting trusted .dmbs file {trustedFilePath}", trustedFilePath);
241 yield return trustedFilePath;
242 }
243
250 async ValueTask SetNoPromptTrusted(string path, CancellationToken cancellationToken)
251 {
252 var configPath = IOManager.ConcatPath(path, ByondConfigDirectory);
253 await IOManager.CreateDirectory(configPath, cancellationToken);
254
255 var configFilePath = IOManager.ConcatPath(configPath, ByondDreamDaemonConfigFilename);
256 Logger.LogTrace("Disabling trusted prompts in {configFilePath}...", configFilePath);
258 configFilePath,
259 Encoding.UTF8.GetBytes(ByondNoPromptTrustedMode),
260 cancellationToken);
261 }
262
269 async ValueTask InstallDirectX(string path, CancellationToken cancellationToken)
270 {
271 using var lockContext = await SemaphoreSlimContext.Lock(semaphore, cancellationToken);
273 {
274 Logger.LogTrace("DirectX already installed.");
275 return;
276 }
277
278 Logger.LogTrace("Installing DirectX redistributable...");
279
280 // always install it, it's pretty fast and will do better redundancy checking than us
281 var rbdx = IOManager.ConcatPath(path, ByondDXDir);
282
283 try
284 {
285 // noShellExecute because we aren't doing runas shennanigans
286 await using var directXInstaller = await processExecutor.LaunchProcess(
287 IOManager.ConcatPath(rbdx, "DXSETUP.exe"),
288 rbdx,
289 "/silent",
290 cancellationToken,
291 noShellExecute: true);
292
293 int exitCode;
294 using (cancellationToken.Register(() => directXInstaller.Terminate()))
295 exitCode = (await directXInstaller.Lifetime).Value;
296 cancellationToken.ThrowIfCancellationRequested();
297
298 if (exitCode != 0)
299 throw new JobException(ErrorCode.ByondDirectXInstallFail, new JobException($"Invalid exit code: {exitCode}"));
300 installedDirectX = true;
301 }
302 catch (Exception e)
303 {
304 throw new JobException(ErrorCode.ByondDirectXInstallFail, e);
305 }
306 }
307
316 async ValueTask AddDreamDaemonToFirewall(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
317 {
318 var dreamDaemonName = GetDreamDaemonName(version.Version!, out var usesDDExe);
319
320 var dreamDaemonPath = IOManager.ResolvePath(
322 path,
324 dreamDaemonName));
325
326 int exitCode;
327 try
328 {
329 // I really wish we could add the instance name here but
330 // 1. It'd make IByondInstaller need to be transient per-instance and WindowsByondInstaller relys on being a singleton for its DX installer call
331 // 2. The instance could be renamed, so it'd have to be an unfriendly ID anyway.
332 var ruleName = $"TGS DreamDaemon {version}";
333
336 Logger,
337 ruleName,
338 dreamDaemonPath,
339 deploymentPipelineProcesses && sessionConfiguration.LowPriorityDeploymentProcesses,
340 cancellationToken);
341 }
342 catch (Exception ex)
343 {
344 throw new JobException(ErrorCode.EngineFirewallFail, ex);
345 }
346
347 if (exitCode != 0)
348 throw new JobException(ErrorCode.EngineFirewallFail, new JobException($"Invalid exit code: {exitCode}"));
349
350 if (usesDDExe)
353 Array.Empty<byte>(),
354 cancellationToken);
355 }
356 }
357}
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.
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...
readonly GeneralConfiguration generalConfiguration
The GeneralConfiguration for the WindowsByondInstaller.
override string GetDreamDaemonName(Version byondVersion, out bool supportsCli)
readonly IProcessExecutor processExecutor
The IProcessExecutor for the WindowsByondInstaller.
override ValueTask Install(EngineVersion version, string path, bool deploymentPipelineProcesses, CancellationToken cancellationToken)
Does actions necessary to get an extracted installation working.A ValueTask representing the running ...
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...
WindowsByondInstaller(IProcessExecutor processExecutor, IIOManager ioManager, IFileDownloader fileDownloader, IOptions< GeneralConfiguration > generalConfigurationOptions, IOptions< SessionConfiguration > sessionConfigurationOptions, ILogger< WindowsByondInstaller > logger)
Initializes a new instance of the WindowsByondInstaller class.
static readonly SemaphoreSlim UserFilesSemaphore
SemaphoreSlim for writing to files in the user's BYOND directory.
bool SkipAddingByondFirewallException
If the netsh.exe execution to exempt DreamDaemon from Windows firewall should be skipped.
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:13
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