1 using Byond.TopicSender;
2 using Microsoft.Extensions.Logging;
10 using System.Threading.Tasks;
93 readonly ILogger<SessionControllerFactory>
logger;
107 return securityLevel
switch 112 _ =>
throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture,
"Bad DreamDaemon security level: {0}", securityLevel)),
122 using var socket =
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
126 socket.Bind(
new IPEndPoint(IPAddress.Any, port));
154 ITopicClient byondTopicSender,
163 ILoggerFactory loggerFactory,
164 ILogger<SessionControllerFactory> logger,
165 Api.Models.Instance instance)
167 this.processExecutor = processExecutor ??
throw new ArgumentNullException(nameof(processExecutor));
168 this.byond = byond ??
throw new ArgumentNullException(nameof(byond));
169 this.byondTopicSender = byondTopicSender ??
throw new ArgumentNullException(nameof(byondTopicSender));
170 this.cryptographySuite = cryptographySuite ??
throw new ArgumentNullException(nameof(cryptographySuite));
171 this.assemblyInformationProvider = assemblyInformationProvider ??
throw new ArgumentNullException(nameof(assemblyInformationProvider));
172 this.instance = instance ??
throw new ArgumentNullException(nameof(instance));
173 this.ioManager = ioManager ??
throw new ArgumentNullException(nameof(ioManager));
174 this.chat = chat ??
throw new ArgumentNullException(nameof(chat));
175 this.networkPromptReaper = networkPromptReaper ??
throw new ArgumentNullException(nameof(networkPromptReaper));
176 this.platformIdentifier = platformIdentifier ??
throw new ArgumentNullException(nameof(platformIdentifier));
177 this.bridgeRegistrar = bridgeRegistrar ??
throw new ArgumentNullException(nameof(bridgeRegistrar));
178 this.serverPortProvider = serverPortProvider ??
throw new ArgumentNullException(nameof(serverPortProvider));
179 this.loggerFactory = loggerFactory ??
throw new ArgumentNullException(nameof(loggerFactory));
180 this.logger = logger ??
throw new ArgumentNullException(nameof(logger));
184 #pragma warning disable CA1506 // TODO: Decomplexify 190 bool primaryDirectory,
192 CancellationToken cancellationToken)
195 if (!portToUse.HasValue)
196 throw new InvalidOperationException(
"Given port is null!");
212 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid DreamDaemonSecurity value: {0}", dmbProvider.
CompileJob.
MinimumSecurityLevel));
215 var chatTrackingContext = chat.CreateTrackingContext();
219 var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.
CompileJob.
ByondVersion), cancellationToken).ConfigureAwait(
false);
223 await byondLock.
TrustDmbPath(ioManager.ConcatPath(basePath, dmbProvider.
DmbName), cancellationToken).ConfigureAwait(
false);
225 PortBindTest(portToUse.Value);
226 await CheckPagerIsNotRunning(cancellationToken).ConfigureAwait(
false);
228 var accessIdentifier = cryptographySuite.GetSecureString();
232 var parameters = $
"{DMApiConstants.ParamApiVersion}={byondTopicSender.SanitizeString(DMApiConstants.Version.Semver().ToString())}&{byondTopicSender.SanitizeString(DMApiConstants.ParamServerPort)}={serverPortProvider.HttpApiPort}&{byondTopicSender.SanitizeString(DMApiConstants.ParamAccessIdentifier)}={byondTopicSender.SanitizeString(accessIdentifier)}";
234 var visibility = apiValidate ?
"invisible" :
"public";
237 Guid? logFileGuid = null;
238 var arguments = String.Format(
239 CultureInfo.InvariantCulture,
240 "{0} -port {1} -ports 1-65535 {2}-close -{3} -{4}{5} -public -params \"{6}\"",
243 launchParameters.
AllowWebClient.Value ?
"-webclient " : String.Empty,
246 platformIdentifier.IsWindows
247 ? $
" -log {logFileGuid = Guid.NewGuid()}" 252 var noShellExecute = !platformIdentifier.IsWindows;
255 var process = processExecutor.LaunchProcess(
256 byondLock.DreamDaemonPath,
261 noShellExecute: noShellExecute);
263 async Task<string> GetDDOutput()
265 if (!platformIdentifier.IsWindows)
266 return process.GetCombinedOutput();
268 var logFilePath = ioManager.ConcatPath(basePath, logFileGuid.ToString());
271 var dreamDaemonLogBytes = await ioManager.ReadAllBytes(
274 .ConfigureAwait(
false);
276 return Encoding.UTF8.GetString(dreamDaemonLogBytes);
282 await ioManager.DeleteFile(logFilePath,
default).ConfigureAwait(
false);
286 logger.LogWarning(
"Failed to delete DreamDaemon log file {0}: {1}", logFilePath, ex);
292 _ = process.Lifetime.ContinueWith(
297 var ddOutput = await GetDDOutput().ConfigureAwait(
false);
299 "DreamDaemon Output:{0}{1}",
300 Environment.NewLine, ddOutput);
304 logger.LogWarning(
"Error reading DreamDaemon output: {0}", ex);
307 TaskScheduler.Current);
311 networkPromptReaper.RegisterProcess(process);
313 var runtimeInformation = CreateRuntimeInformation(
336 assemblyInformationProvider,
341 return sessionController;
352 if (currentByondLock == null)
359 chatTrackingContext.Dispose();
363 #pragma warning restore CA1506 368 CancellationToken cancellationToken)
370 if (reattachInformation == null)
371 throw new ArgumentNullException(nameof(reattachInformation));
373 var chatTrackingContext = chat.CreateTrackingContext();
376 var byondLock = await byond.UseExecutables(Version.Parse(reattachInformation.
Dmb.
CompileJob.
ByondVersion), cancellationToken).ConfigureAwait(
false);
379 var process = processExecutor.GetProcess(reattachInformation.
ProcessId);
385 networkPromptReaper.RegisterProcess(process);
386 var runtimeInformation = CreateRuntimeInformation(
387 reattachInformation.
Dmb,
402 assemblyInformationProvider,
409 chatTrackingContext = null;
420 byondLock?.Dispose();
425 chatTrackingContext?.Dispose();
444 bool apiValidateOnly)
446 var revisionInfo =
new Api.Models.Internal.RevisionInformation
452 var testMerges = dmbProvider
456 .Select(x => x.TestMerge)
461 assemblyInformationProvider,
478 if (!platformIdentifier.IsWindows)
481 using var otherProcess = processExecutor.GetProcessByName(
"byond");
482 if (otherProcess == null)
485 var otherUsernameTask = otherProcess.GetExecutingUsername(cancellationToken);
486 using var ourProcess = processExecutor.GetCurrentProcess();
487 var ourUserName = await ourProcess.GetExecutingUsername(cancellationToken).ConfigureAwait(
false);
488 var otherUserName = await otherUsernameTask.ConfigureAwait(
false);
490 if(otherUserName.Equals(ourUserName, StringComparison.Ordinal))
CompileJob CompileJob
The CompileJob of the .dmb
async Task< ISessionController > Reattach(ReattachInformation reattachInformation, CancellationToken cancellationToken)
Create a ISessionController from an existing DreamDaemon instance
readonly IIOManager ioManager
The IIOManager for the SessionControllerFactory
ErrorCode
Types of ErrorMessages that the API may return.
async Task< ISessionController > LaunchNew(IDmbProvider dmbProvider, IByondExecutableLock currentByondLock, DreamDaemonLaunchParameters launchParameters, bool primaryPort, bool primaryDirectory, bool apiValidate, CancellationToken cancellationToken)
Create a ISessionController from a freshly launch DreamDaemon instance
SessionControllerFactory(IProcessExecutor processExecutor, IByondManager byond, ITopicClient byondTopicSender, ICryptographySuite cryptographySuite, IAssemblyInformationProvider assemblyInformationProvider, IIOManager ioManager, IChatManager chat, INetworkPromptReaper networkPromptReaper, IPlatformIdentifier platformIdentifier, IBridgeRegistrar bridgeRegistrar, IServerPortProvider serverPortProvider, ILoggerFactory loggerFactory, ILogger< SessionControllerFactory > logger, Api.Models.Instance instance)
Construct a SessionControllerFactory
readonly ILoggerFactory loggerFactory
The ILoggerFactory for the SessionControllerFactory
Use server authentication
readonly IChatManager chat
The IChatManager for the SessionControllerFactory
readonly INetworkPromptReaper networkPromptReaper
The INetworkPromptReaper for the SessionControllerFactory
RevisionInformation RevisionInformation
Git revision the compiler ran on. Not modifiable
static string SecurityWord(DreamDaemonSecurity securityLevel)
Change a given securityLevel into the appropriate DreamDaemon command line word
Launch settings for DreamDaemon
readonly IPlatformIdentifier platformIdentifier
The IPlatformIdentifier for the SessionControllerFactory
DreamDaemonSecurity SecurityLevel
The DreamDaemonSecurity level of DreamDaemon
readonly ILogger< SessionControllerFactory > logger
The ILogger for the SessionControllerFactory
RuntimeInformation CreateRuntimeInformation(IDmbProvider dmbProvider, IChatTrackingContext chatTrackingContext, DreamDaemonSecurity?securityLevel, bool apiValidateOnly)
Create RuntimeInformation.
Contains various cryptographic functions
readonly ICryptographySuite cryptographySuite
The ICryptographySuite for the SessionControllerFactory
For managing connected chat services
readonly ITopicClient byondTopicSender
The ITopicClient for the SessionControllerFactory
ushort SecondaryPort
The second port DreamDaemon uses
DreamDaemonSecurity MinimumSecurityLevel
The minimum DreamDaemonSecurity required to run the CompileJob's output
Version ByondVersion
The Byond.Version the CompileJob was made with
Operation exceptions thrown from the context of a Models.Job
string PrimaryDirectory
The primary game directory with a trailing directory separator
string SecondaryDirectory
The secondary game directory with a trailing directory separator
IReadOnlyCollection< ChannelRepresentation > Channels
IReadOnlyCollection<T> of ChannelRepresentations in the IChatTrackingContext.
Factory for ISessionControllers
Implements a fake "dead" ISessionController
async Task CheckPagerIsNotRunning(CancellationToken cancellationToken)
Make sure the BYOND pager is not running.
For managing the BYOND installation
readonly IServerPortProvider serverPortProvider
The IServerPortProvider for the SessionControllerFactory.
Represents usage of the two primary BYOND server executables
Task TrustDmbPath(string fullDmbPath, CancellationToken cancellationToken)
Add a given fullDmbPath to the trusted DMBs list in BYOND's config.
Handles communication with a DreamDaemon IProcess
readonly IByondManager byond
The IByondManager for the SessionControllerFactory
On Windows, DreamDaemon will show an unskippable prompt when using /world/proc/OpenPort(). This looks out for those prompts and immediately clicks "Yes" if the owning process has registered for it
readonly Api.Models.Instance instance
The Api.Models.Instance for the SessionControllerFactory
Represents a BYOND installation
Represents a tracking of dynamic chat json files
readonly IAssemblyInformationProvider assemblyInformationProvider
The IAssemblyInformationProvider for the SessionControllerFactory
DreamDaemonSecurity
DreamDaemon's security level
string DmbName
The file name of the .dmb
Provides absolute paths to the latest compiled .dmbs
Interface for using filesystems
Registers IBridgeHandlers.
Provides access to the server's HttpApiPort.
readonly IBridgeRegistrar bridgeRegistrar
The IBridgeRegistrar for the SessionControllerFactory.
static void PortBindTest(ushort port)
Check if a given port can be bound to.
ushort PrimaryPort
The first port DreamDaemon uses. This should be the publically advertised port
readonly IProcessExecutor processExecutor
The IProcessExecutor for the SessionControllerFactory
For launching IProcess'
uint StartupTimeout
The DreamDaemon startup timeout in seconds
bool AllowWebClient
If the BYOND web client can be used to connect to the game server