1 using Microsoft.Extensions.Logging;
9 using System.Threading.Tasks;
92 readonly ILogger<SessionControllerFactory>
logger;
106 return securityLevel
switch 111 _ =>
throw new ArgumentOutOfRangeException(nameof(securityLevel), securityLevel, String.Format(CultureInfo.InvariantCulture,
"Bad DreamDaemon security level: {0}", securityLevel)),
121 using var socket =
new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
125 socket.Bind(
new IPEndPoint(IPAddress.Any, port));
162 ILoggerFactory loggerFactory,
163 ILogger<SessionControllerFactory> logger,
164 Api.Models.Instance instance)
166 this.processExecutor = processExecutor ??
throw new ArgumentNullException(nameof(processExecutor));
167 this.byond = byond ??
throw new ArgumentNullException(nameof(byond));
168 this.topicClientFactory = topicClientFactory ??
throw new ArgumentNullException(nameof(topicClientFactory));
169 this.cryptographySuite = cryptographySuite ??
throw new ArgumentNullException(nameof(cryptographySuite));
170 this.assemblyInformationProvider = assemblyInformationProvider ??
throw new ArgumentNullException(nameof(assemblyInformationProvider));
171 this.instance = instance ??
throw new ArgumentNullException(nameof(instance));
172 this.ioManager = ioManager ??
throw new ArgumentNullException(nameof(ioManager));
173 this.chat = chat ??
throw new ArgumentNullException(nameof(chat));
174 this.networkPromptReaper = networkPromptReaper ??
throw new ArgumentNullException(nameof(networkPromptReaper));
175 this.platformIdentifier = platformIdentifier ??
throw new ArgumentNullException(nameof(platformIdentifier));
176 this.bridgeRegistrar = bridgeRegistrar ??
throw new ArgumentNullException(nameof(bridgeRegistrar));
177 this.serverPortProvider = serverPortProvider ??
throw new ArgumentNullException(nameof(serverPortProvider));
178 this.loggerFactory = loggerFactory ??
throw new ArgumentNullException(nameof(loggerFactory));
179 this.logger = logger ??
throw new ArgumentNullException(nameof(logger));
183 #pragma warning disable CA1506 // TODO: Decomplexify 189 CancellationToken cancellationToken)
191 logger.LogTrace(
"Begin session launch...");
192 if (!launchParameters.
Port.HasValue)
193 throw new InvalidOperationException(
"Given port is null!");
206 throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture,
"Invalid DreamDaemonSecurity value: {0}", dmbProvider.
CompileJob.
MinimumSecurityLevel));
209 var chatTrackingContext = chat.CreateTrackingContext();
213 var byondLock = currentByondLock ?? await byond.UseExecutables(Version.Parse(dmbProvider.
CompileJob.
ByondVersion), cancellationToken).ConfigureAwait(
false);
217 "Launching session with CompileJob {0}...",
218 byondLock.Version.Semver(),
222 await byondLock.
TrustDmbPath(ioManager.ConcatPath(dmbProvider.
Directory, dmbProvider.
DmbName), cancellationToken).ConfigureAwait(
false);
224 PortBindTest(launchParameters.
Port.Value);
225 await CheckPagerIsNotRunning(cancellationToken).ConfigureAwait(
false);
227 var accessIdentifier = cryptographySuite.GetSecureString();
229 var byondTopicSender = topicClientFactory.CreateTopicClient(
230 TimeSpan.FromMilliseconds(
235 var parameters = $
"{DMApiConstants.ParamApiVersion}={byondTopicSender.SanitizeString(DMApiConstants.Version.Semver().ToString())}&{byondTopicSender.SanitizeString(DMApiConstants.ParamServerPort)}={serverPortProvider.HttpApiPort}&{byondTopicSender.SanitizeString(DMApiConstants.ParamAccessIdentifier)}={byondTopicSender.SanitizeString(accessIdentifier)}";
237 var visibility = apiValidate ?
"invisible" :
"public";
240 Guid? logFileGuid = null;
241 var arguments = String.Format(
242 CultureInfo.InvariantCulture,
243 "{0} -port {1} -ports 1-65535 {2}-close -{3} -{4}{5} -public -params \"{6}\"",
245 launchParameters.
Port.Value,
246 launchParameters.
AllowWebClient.Value ?
"-webclient " : String.Empty,
249 platformIdentifier.IsWindows
250 ? $
" -log {logFileGuid = Guid.NewGuid()}" 255 var noShellExecute = !platformIdentifier.IsWindows;
258 logger.LogDebug(
"Session will have no DMAPI support!");
261 var process = processExecutor.LaunchProcess(
262 byondLock.DreamDaemonPath,
267 noShellExecute: noShellExecute);
269 async Task<string> GetDDOutput()
271 if (!platformIdentifier.IsWindows)
272 return process.GetCombinedOutput();
274 var logFilePath = ioManager.ConcatPath(dmbProvider.
Directory, logFileGuid.ToString());
277 var dreamDaemonLogBytes = await ioManager.ReadAllBytes(
280 .ConfigureAwait(
false);
282 return Encoding.UTF8.GetString(dreamDaemonLogBytes);
288 await ioManager.DeleteFile(logFilePath,
default).ConfigureAwait(
false);
292 logger.LogWarning(
"Failed to delete DreamDaemon log file {0}: {1}", logFilePath, ex);
298 _ = process.Lifetime.ContinueWith(
303 var ddOutput = await GetDDOutput().ConfigureAwait(
false);
305 "DreamDaemon Output:{0}{1}",
306 Environment.NewLine, ddOutput);
310 logger.LogWarning(
"Error reading DreamDaemon output: {0}", ex);
313 TaskScheduler.Current);
317 networkPromptReaper.RegisterProcess(process);
319 var runtimeInformation = CreateRuntimeInformation(
330 launchParameters.
Port.Value);
341 assemblyInformationProvider,
347 return sessionController;
358 if (currentByondLock == null)
365 chatTrackingContext.Dispose();
369 #pragma warning restore CA1506 374 CancellationToken cancellationToken)
376 if (reattachInformation == null)
377 throw new ArgumentNullException(nameof(reattachInformation));
379 logger.LogTrace(
"Begin session reattach...");
380 var byondTopicSender = topicClientFactory.CreateTopicClient(reattachInformation.
TopicRequestTimeout);
381 var chatTrackingContext = chat.CreateTrackingContext();
384 var byondLock = await byond.UseExecutables(Version.Parse(reattachInformation.
Dmb.
CompileJob.
ByondVersion), cancellationToken).ConfigureAwait(
false);
389 "Attaching to session PID: {0}, CompileJob: {1}...",
393 var process = processExecutor.GetProcess(reattachInformation.
ProcessId);
399 networkPromptReaper.RegisterProcess(process);
400 var runtimeInformation = CreateRuntimeInformation(
401 reattachInformation.
Dmb,
416 assemblyInformationProvider,
424 chatTrackingContext = null;
435 byondLock?.Dispose();
440 chatTrackingContext?.Dispose();
456 bool apiValidateOnly)
458 var revisionInfo =
new Api.Models.Internal.RevisionInformation
464 var testMerges = dmbProvider
468 .Select(x => x.TestMerge)
473 assemblyInformationProvider,
490 if (!platformIdentifier.IsWindows)
493 using var otherProcess = processExecutor.GetProcessByName(
"byond");
494 if (otherProcess == null)
497 var otherUsernameTask = otherProcess.GetExecutingUsername(cancellationToken);
498 using var ourProcess = processExecutor.GetCurrentProcess();
499 var ourUserName = await ourProcess.GetExecutingUsername(cancellationToken).ConfigureAwait(
false);
500 var otherUserName = await otherUsernameTask.ConfigureAwait(
false);
502 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
long Id
The ID of the entity.
ErrorCode
Types of ErrorMessages that the API may return.
readonly ILoggerFactory loggerFactory
The ILoggerFactory for the SessionControllerFactory
ushort Port
The first port DreamDaemon uses. This should be the publically advertised port
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
uint TopicRequestTimeout
The timeout for sending and receiving BYOND topics in milliseconds.
virtual Version DMApiVersion
The DMAPI Version.
RuntimeInformation CreateRuntimeInformation(IDmbProvider dmbProvider, IChatTrackingContext chatTrackingContext, DreamDaemonSecurity?securityLevel, bool apiValidateOnly)
Create RuntimeInformation.
Factory for ITopicClients
Contains various cryptographic functions
readonly ICryptographySuite cryptographySuite
The ICryptographySuite for the SessionControllerFactory
For managing connected chat services
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
IReadOnlyCollection< ChannelRepresentation > Channels
IReadOnlyCollection<T> of ChannelRepresentations in the IChatTrackingContext.
Factory for ISessionControllers
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.
string Directory
The primary game directory with a trailing directory separator
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.
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
async Task< ISessionController > LaunchNew(IDmbProvider dmbProvider, IByondExecutableLock currentByondLock, DreamDaemonLaunchParameters launchParameters, bool apiValidate, CancellationToken cancellationToken)
Create a ISessionController from a freshly launch DreamDaemon instance
readonly Api.Models.Instance instance
The Api.Models.Instance for the SessionControllerFactory
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.
SessionControllerFactory(IProcessExecutor processExecutor, IByondManager byond, ITopicClientFactory topicClientFactory, 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 IProcessExecutor processExecutor
The IProcessExecutor for the SessionControllerFactory
For launching IProcess'
uint StartupTimeout
The DreamDaemon startup timeout in seconds
readonly ITopicClientFactory topicClientFactory
The ITopicClientFactory for the SessionControllerFactory
bool AllowWebClient
If the BYOND web client can be used to connect to the game server