private bool VerifyVersionData(ServerCommandData data)
        {
            CoreLog.Log(CoreLogLevel.Debug, $"Received {data.Command} while waiting for server announcement.");
            if (data.Command == ServerCommand.Announce)
            {
                var protocolVersion = ValueConverter.ToUInt32(data.Data, 0);
                var pluginVersion   = ValueConverter.ToVersion(data.Data, 4);

                if (protocolVersion != CoreVersion.ProtocolVersion)
                {
                    var updatee = protocolVersion > CoreVersion.ProtocolVersion ? "game mode" : "server";
                    CoreLog.Log(CoreLogLevel.Error,
                                $"Protocol version mismatch! The server is running protocol {protocolVersion} but the game mode is running {CoreVersion.ProtocolVersion}");
                    CoreLog.Log(CoreLogLevel.Error, $"Please update your {updatee}.");
                    return(false);
                }
                CoreLog.Log(CoreLogLevel.Info, $"Connected to version {pluginVersion.ToString(3)} via protocol {protocolVersion}");

                ServerPath = ValueConverter.ToString(data.Data, 8, Encoding.ASCII);

                return(true);
            }
            CoreLog.Log(CoreLogLevel.Error, $"Received command {data.Command.ToString().ToLower()} instead of announce.");
            return(false);
        }
        /// <summary>
        ///     Waits for the next command sent by the server.
        /// </summary>
        /// <returns>The command sent by the server.</returns>
        public virtual async Task<ServerCommandData> ReceiveAsync()
        {
            AssertNotDisposed();

            while (true)
            {
                if (_buffer.TryPop(out var command))
                    return command;

                try
                {
                    var task = _stream?.ReadAsync(_readBuffer, 0, _readBuffer.Length, _source.Token);

                    if (task == null)
                        throw new StreamCommunicationClientClosedException();

                    var len = await task;

                    if (_stream == null)
                        throw new StreamCommunicationClientClosedException();

                    if (_readBuffer.Length == len)
                    {
                        CoreLog.Log(CoreLogLevel.Error, "Network buffer overflow detected!");
                    }

                    _buffer.Push(_readBuffer, 0, len);
                }
                catch (TaskCanceledException)
                {
                    throw new StreamCommunicationClientClosedException();
                }
            }
        }
        private async Task NetworkingRoutine()
        {
            try
            {
                while (_running)
                {
                    var data = await CommunicationClient.ReceiveAsync();

                    if (!_running)
                    {
                        return;
                    }

                    _commandWaitQueue.Release(data);
                }
            }
            catch (StreamCommunicationClientClosedException)
            {
                CoreLog.Log(CoreLogLevel.Warning, "Network routine ended because the communication with the SA:MP server was closed.");
            }
            catch (Exception e)
            {
                CoreLog.Log(CoreLogLevel.Error, "Network routine died! " + e);
            }
        }
Beispiel #4
0
        /// <inheritdoc />
        public void Tick()
        {
            if (!_didInitialize || _timers.Count == 0)
            {
                return;
            }

            var timestamp = Stopwatch.GetTimestamp();

            // Don't user foreach for performance reasons
            // ReSharper disable once ForCanBeConvertedToForeach
            for (var i = 0; i < _timers.Count; i++)
            {
                var timer = _timers[i];

                while ((timer.NextTick > _lastTick || timestamp < _lastTick) && timer.NextTick <= timestamp)
                {
                    try
                    {
                        timer.Invoke();
                    }
                    catch (Exception e)
                    {
                        CoreLog.Log(CoreLogLevel.Error, $"Timer threw an exception: {e}");
                    }

                    timer.NextTick += timer.IntervalTicks;
                }
            }

            _lastTick = timestamp;
        }
        /// <summary>
        ///     Runs the game mode of this runner.
        /// </summary>
        /// <returns>true if shut down by the game mode, false otherwise.</returns>
        /// <exception cref="Exception">Thrown if a game mode is already running.</exception>
        public bool Run()
        {
            InternalStorage.RunningClient = this;

            // Prepare the syncronization context
            _messageQueue           = new NoWaitMessageQueue();
            _synchronizationContext = new SampSharpSynchronizationContext(_messageQueue);

            SynchronizationContext.SetSynchronizationContext(_synchronizationContext);

            _mainThread = Thread.CurrentThread.ManagedThreadId;
            _running    = true;

            CoreLog.Log(CoreLogLevel.Initialisation, "SampSharp GameMode Client");
            CoreLog.Log(CoreLogLevel.Initialisation, "-------------------------");
            CoreLog.Log(CoreLogLevel.Initialisation, $"v{CoreVersion.Version.ToString(3)}, (C)2014-2020 Tim Potze");
            CoreLog.Log(CoreLogLevel.Initialisation, "Hosted run mode is active.");
            CoreLog.Log(CoreLogLevel.Initialisation, "");

            // TODO: Verify plugin version

            _gameModeProvider.Initialize(this);

            return(true);
        }
        private async void Initialize()
        {
            _mainThread = Thread.CurrentThread.ManagedThreadId;

            CoreLog.Log(CoreLogLevel.Initialisation, "SampSharp GameMode Client");
            CoreLog.Log(CoreLogLevel.Initialisation, "-------------------------");
            CoreLog.Log(CoreLogLevel.Initialisation, $"v{CoreVersion.Version.ToString(3)}, (C)2014-2020 Tim Potze");
            CoreLog.Log(CoreLogLevel.Initialisation, "Multi-process run mode is active. FOR DEVELOPMENT PURPOSES ONLY!");
            CoreLog.Log(CoreLogLevel.Initialisation, "Run your server in hosted run mode for production environments. See https://sampsharp.net/running-in-production for more information.");
            CoreLog.Log(CoreLogLevel.Initialisation, "");

            AppDomain.CurrentDomain.ProcessExit += (sender, args) =>
            {
                CoreLog.Log(CoreLogLevel.Info, "Shutdown signal received");
                ShutDown();

                if (_mainRoutine != null && !_mainRoutine.IsCompleted)
                {
                    _mainRoutine.Wait();
                }

                if (_networkingRoutine != null && !_networkingRoutine.IsCompleted)
                {
                    _networkingRoutine.Wait();
                }
            };

            CoreLog.Log(CoreLogLevel.Info, $"Connecting to the server via {CommunicationClient}...");
            await CommunicationClient.Connect();

            _running = true;

            CoreLog.Log(CoreLogLevel.Info, "Set up networking routine...");
            StartNetworkingRoutine();

            CoreLog.Log(CoreLogLevel.Info, "Connected! Waiting for server announcement...");
            ServerCommandData data;

            do
            {
                data = await _commandWaitQueue.WaitAsync();

                // Could receive ticks if reconnecting.
            } while (data.Command == ServerCommand.Tick);

            if (!VerifyVersionData(data))
            {
                return;
            }

            CoreLog.Log(CoreLogLevel.Info, "Initializing game mode provider...");
            _gameModeProvider.Initialize(this);

            CoreLog.Log(CoreLogLevel.Info, "Sending start signal to server...");
            Send(ServerCommand.Start, new[] { (byte)_startBehaviour });

            CoreLog.Log(CoreLogLevel.Info, "Set up main routine...");
            _mainRoutine = MainRoutine();
        }
        /// <summary>
        ///     Registers the specified command.
        /// </summary>
        /// <param name="command">The command.</param>
        public virtual void Register(ICommand command)
        {
            if (command == null)
            {
                throw new ArgumentNullException(nameof(command));
            }

            CoreLog.Log(CoreLogLevel.Debug, $"Registering command {command}");
            _commands.Add(command);
        }
Beispiel #8
0
        /// <summary>
        ///     Invokes the native with the specified arguments.
        /// </summary>
        /// <param name="arguments">The arguments.</param>
        /// <returns>The return value of the native.</returns>
        public int Invoke(params object[] arguments)
        {
            if (arguments == null)
            {
                throw new ArgumentNullException(nameof(arguments));
            }

            if (Parameters.Length != arguments.Length)
            {
                throw new ArgumentOutOfRangeException(nameof(arguments), "Invalid argument count");
            }

            IEnumerable <byte> data = ValueConverter.GetBytes(Handle);

            if (CoreLog.DoesLog(CoreLogLevel.Verbose))
            {
                CoreLog.LogVerbose("Invoking {0}({1})", Name, string.Join(", ", arguments));
            }

            int length;

            for (var i = 0; i < Parameters.Length; i++)
            {
                length = GetLength(i, arguments);

                data = data
                       .Concat(new[] { (byte)Parameters[i].ArgumentType })
                       .Concat(Parameters[i].GetBytes(arguments[i], length, _gameModeClient));
            }

            var response = _gameModeClient.InvokeNative(data);

            if (response.Length < 4)
            {
                CoreLog.Log(CoreLogLevel.Warning, "Native call returned no response, execution probably failed.");
                return(0);
            }

            var result  = ValueConverter.ToInt32(response, 0);
            var respPos = 4;

            for (var i = 0; i < Parameters.Length; i++)
            {
                length = GetLength(i, arguments);

                var value = Parameters[i].GetReferenceArgument(response, ref respPos, length, result, _gameModeClient);
                if (value != null)
                {
                    arguments[i] = value;
                }
            }

            return(result);
        }
Beispiel #9
0
 /// <summary>
 ///     Autoloads the controllers in the specified assembly.
 /// </summary>
 /// <param name="assembly">The assembly.</param>
 public void AutoloadControllersForAssembly(Assembly assembly)
 {
     foreach (var type in assembly.GetExportedTypes()
              .Where(t => t.GetTypeInfo().IsClass&&
                     typeof(IController).GetTypeInfo().IsAssignableFrom(t) &&
                     t.GetTypeInfo().GetCustomAttribute <ControllerAttribute>() != null))
     {
         CoreLog.Log(CoreLogLevel.Debug, $"Autoloading type {type}...");
         _controllers.Override(Activator.CreateInstance(type) as IController);
     }
 }
Beispiel #10
0
        private void AutoloadPoolTypes()
        {
            var types = new List <Type>();

            foreach (var poolType in new[] { typeof(BaseMode), GetType() }.Concat(_extensions.Select(e => e.GetType()))
                     .Select(t => t.GetTypeInfo().Assembly)
                     .Distinct()
                     .SelectMany(a => a.GetTypes())
                     .Where(t => t.GetTypeInfo().IsClass&& t.GetTypeInfo().GetCustomAttribute <PooledTypeAttribute>() != null)
                     .Distinct())
            {
                // If poolType or subclass of poolType is already in types, continue.
                if (types.Any(t => t == poolType || poolType.GetTypeInfo().IsAssignableFrom(t)))
                {
                    CoreLog.Log(CoreLogLevel.Debug,
                                $"Pool of type {poolType} is not autoloaded because a subclass of it will already be loaded.");
                    continue;
                }

                // Remove all types in types where type is supertype of poolType.
                foreach (var t in types.Where(t => t.GetTypeInfo().IsAssignableFrom(poolType)).ToArray())
                {
                    CoreLog.Log(CoreLogLevel.Debug,
                                $"No longer autoloading type {poolType} because a subclass of it is going to be loaded.");
                    types.Remove(t);
                }

                CoreLog.Log(CoreLogLevel.Debug, $"Autoloading pool of type {poolType}.");
                types.Add(poolType);
            }

            var poolTypes = new[]
            {
                typeof(IdentifiedPool <>),
                typeof(IdentifiedOwnedPool <,>)
            };

            foreach (var type in types)
            {
                var pool = type;
                do
                {
                    pool = pool.GetTypeInfo().BaseType;
                } while (pool != null && (!pool.GetTypeInfo().IsGenericType || !poolTypes.Contains(pool.GetGenericTypeDefinition())));

                if (pool == null)
                {
                    CoreLog.Log(CoreLogLevel.Debug, $"Skipped autoloading pool of type {type} because it's not a subtype of a pool.");
                    continue;
                }

                pool.GetTypeInfo().GetMethod("Register", new[] { typeof(Type) }).Invoke(null, new object[] { type });
            }
        }
Beispiel #11
0
        private void CreateTimersFromAssemblies(long tick)
        {
            // Find methods with TimerAttribute in any ISystem in any assembly.
            var events = new AssemblyScanner()
                         .IncludeAllAssemblies()
                         .IncludeNonPublicMembers()
                         .Implements <ISystem>()
                         .ScanMethods <TimerAttribute>();

            // Create timer invokers and store timer info in registry.
            foreach (var(method, attribute) in events)
            {
                CoreLog.LogDebug("Adding timer on {0}.{1}.", method.DeclaringType, method.Name);

                if (!IsValidInterval(attribute.IntervalTimeSpan))
                {
                    CoreLog.Log(CoreLogLevel.Error, $"Timer {method} could not be registered the interval {attribute.IntervalTimeSpan} is invalid.");
                    continue;
                }

                var service = _serviceProvider.GetService(method.DeclaringType);

                if (service == null)
                {
                    CoreLog.Log(CoreLogLevel.Debug, "Skipping timer registration because service could not be loaded.");
                    continue;
                }

                var parameterInfos = method.GetParameters()
                                     .Select(info => new MethodParameterSource(info)
                {
                    IsService = true
                })
                                     .ToArray();

                var compiled = MethodInvokerFactory.Compile(method, parameterInfos);

                if (attribute.IntervalTimeSpan < LowInterval)
                {
                    CoreLog.Log(CoreLogLevel.Warning, $"Timer {method.DeclaringType}.{method.Name} has a low interval of {attribute.IntervalTimeSpan}.");
                }

                var timer = new TimerInfo
                {
                    IsActive      = true,
                    Invoke        = () => compiled(service, null, _serviceProvider, null),
                    IntervalTicks = attribute.IntervalTimeSpan.Ticks,
                    NextTick      = tick + attribute.IntervalTimeSpan.Ticks
                };

                _timers.Add(timer);
            }
        }
Beispiel #12
0
        private void LoadExtensions()
        {
            var load = new List <Assembly>();

            // Create a dependency-ordered list of extensions.
            var loading = new List <Assembly>();

            foreach (
                var assembly in
                GetType()
                .GetTypeInfo()
                .Assembly.GetReferencedAssemblies()
                .Select(Assembly.Load)
                .Concat(new[] { GetType().GetTypeInfo().Assembly })
                .Distinct()
                .Where(a => a.GetCustomAttributes <SampSharpExtensionAttribute>().Any()))
            {
                AddExtensionToLoadList(assembly, load, loading);
            }

            // Load extensions according to dependency list.
            foreach (var assembly in load)
            {
                var attributes = assembly.GetCustomAttributes <SampSharpExtensionAttribute>();

                foreach (var extensionType in attributes.Select(attribute => attribute.Type))
                {
                    if (extensionType == null)
                    {
                        continue;
                    }
                    if (!typeof(IExtension).GetTypeInfo().IsAssignableFrom(extensionType))
                    {
                        CoreLog.Log(CoreLogLevel.Warning, $"The extension from {assembly} could not be loaded. The specified extension type does not inherit from IExtension.");
                        continue;
                    }
                    if (!extensionType.GetTypeInfo().Assembly.Equals(assembly))
                    {
                        CoreLog.Log(CoreLogLevel.Warning, $"The extension from {assembly} could not be loaded. The specified extension type is not part of the assembly.");
                        continue;
                    }
                    if (_extensions.Any(e => e.GetType() == extensionType))
                    {
                        CoreLog.Log(CoreLogLevel.Warning, $"The extension from {assembly} could not be loaded. The specified extension type was already loaded.");
                        continue;
                    }

                    // Register the extension to the plugin.
                    var extension = (IExtension)Activator.CreateInstance(extensionType);
                    RegisterExtension(extension);
                }
            }
        }
Beispiel #13
0
        /// <summary>
        ///     Registers the type to use when initializing new instances.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <exception cref="System.ArgumentNullException">Thrown if type is null</exception>
        /// <exception cref="System.ArgumentException">type must be of type TInstance;type</exception>
        public static void Register(Type type)
        {
            if (type == null)
            {
                throw new ArgumentNullException(nameof(type));
            }
            if (!typeof(TInstance).GetTypeInfo().IsAssignableFrom(type))
            {
                throw new ArgumentException("type must be of type " + typeof(TInstance), nameof(type));
            }

            CoreLog.Log(CoreLogLevel.Debug, $"Type {type} registered to pool.");
            InstanceType = type;
        }
Beispiel #14
0
        /// <summary>
        ///     Adds a <see cref="IController" /> to this collection and remove controllers it overrides.
        /// </summary>
        /// <param name="controller">The <see cref="IController" /> to add to this collection.</param>
        public void Override(IController controller)
        {
            var overrides = this.Where(c => c.GetType().GetTypeInfo().IsInstanceOfType(controller)).ToArray();

            if (overrides.Any())
            {
                CoreLog.Log(CoreLogLevel.Debug,
                            $"{controller} overrides {string.Join(", ", (object[]) overrides)}");
            }

            foreach (var c in overrides)
            {
                Remove(c);
            }

            _controllers.Add(controller);
        }
        /// <summary>
        /// Invokes the middleware.
        /// </summary>
        public object Invoke(EventContext context, IRconCommandService commandService)
        {
            var result = _next(context);

            if (EventHelper.IsSuccessResponse(result))
            {
                return(result);
            }

            if (context.Arguments[0] is string text)
            {
                return(commandService.Invoke(context.EventServices, text));
            }

            CoreLog.Log(CoreLogLevel.Error, "Invalid command middleware input argument types!");
            return(null);
        }
Beispiel #16
0
        /// <summary>
        ///     Waits for the next command sent by the server.
        /// </summary>
        /// <returns>The command sent by the server.</returns>
        public virtual async Task <ServerCommandData> ReceiveAsync()
        {
            AssertNotDisposed();

            while (true)
            {
                if (_buffer.TryPop(out var command))
                {
                    return(command);
                }

                try
                {
                    var task = _stream?.ReadAsync(_readBuffer, 0, _readBuffer.Length, _source.Token);

                    if (task == null)
                    {
                        throw new StreamCommunicationClientClosedException();
                    }

                    var len = await task;

                    if (_stream == null)
                    {
                        throw new StreamCommunicationClientClosedException();
                    }

                    if (_readBuffer.Length == len)
                    {
                        CoreLog.Log(CoreLogLevel.Error, "Network buffer overflow detected!");

                        // TODO: Could try and detect first valid command, but that could mean some vital commands were lost.
                    }

                    _buffer.Push(_readBuffer, 0, len);
                }
                catch (TaskCanceledException)
                {
                    throw new StreamCommunicationClientClosedException();
                }
            }
        }
Beispiel #17
0
        /// <inheritdoc />
        public void Initialize(IGameModeClient client)
        {
            client.UnhandledException += (sender, args) =>
            {
                CoreLog.Log(CoreLogLevel.Error, "Unhandled exception: " + args.Exception);
            };

            var services = new ServiceCollection();

            services.AddSingleton(client);
            services.AddSingleton(client.NativeLoader);
            Configure(services);

            _serviceProvider = services.BuildServiceProvider();
            _systemRegistry  = _serviceProvider.GetRequiredService <ISystemRegistry>();

            Configure();

            AddWrappedSystemTypes();
        }
Beispiel #18
0
        private async void Initialize()
        {
            CoreLog.Log(CoreLogLevel.Initialisation, "SampSharp GameMode Client");
            CoreLog.Log(CoreLogLevel.Initialisation, "-------------------------");
            CoreLog.Log(CoreLogLevel.Initialisation, $"v{CoreVersion.Version.ToString(3)}, (C)2014-2019 Tim Potze");
            CoreLog.Log(CoreLogLevel.Initialisation, "");

            _mainThread = Thread.CurrentThread.ManagedThreadId;
            _running    = true;

            CoreLog.Log(CoreLogLevel.Info, $"Connecting to the server via {CommunicationClient}...");
            await CommunicationClient.Connect();

            CoreLog.Log(CoreLogLevel.Info, "Set up networking routine...");
            StartNetworkingRoutine();

            CoreLog.Log(CoreLogLevel.Info, "Connected! Waiting for server announcement...");
            ServerCommandData data;

            do
            {
                data = await _commandWaitQueue.WaitAsync();

                // Could receive ticks if reconnecting.
            } while (data.Command == ServerCommand.Tick);

            if (!VerifyVersionData(data))
            {
                return;
            }

            CoreLog.Log(CoreLogLevel.Info, "Initializing game mode provider...");
            _gameModeProvider.Initialize(this);

            CoreLog.Log(CoreLogLevel.Info, "Sending start signal to server...");
            Send(ServerCommand.Start, new[] { (byte)_startBehaviour });

            CoreLog.Log(CoreLogLevel.Info, "Set up main routine...");
            MainRoutine();
        }
Beispiel #19
0
        private InvokerInfo CreateInvoker(MethodInfo method, MethodParameterSource[] parameterInfos,
                                          int callbackParamCount)
        {
            var compiled = MethodInvokerFactory.Compile(method, parameterInfos);

            return(new InvokerInfo
            {
                TargetType = method.DeclaringType,
                Invoke = (instance, eventContext) =>
                {
                    var args = eventContext.Arguments;
                    if (callbackParamCount == args.Length)
                    {
                        return compiled(instance, args, eventContext.EventServices, _entityManager);
                    }

                    CoreLog.Log(CoreLogLevel.Error,
                                $"Callback parameter count mismatch {callbackParamCount} != {args.Length}");
                    return null;
                }
            });
        }
        private void ProcessCommand(ServerCommandData data)
        {
            switch (data.Command)
            {
            case ServerCommand.Nop:
                break;

            case ServerCommand.Tick:
                // The server expects at least a message every 5 seconds or else the debug pause
                // detector kicks in. Send one at least every 3 to be safe.
                if (DateTime.UtcNow - _lastSend > TimeSpan.FromSeconds(3))
                {
                    Send(ServerCommand.Alive, null);
                }

                if (!_canTick)
                {
                    break;
                }

                try
                {
                    _gameModeProvider.Tick();
                }
                catch (Exception e)
                {
                    OnUnhandledException(new UnhandledExceptionEventArgs("Tick", e));
                }

                break;

            case ServerCommand.Pong:
                if (_pongs.Count == 0)
                {
                    CoreLog.Log(CoreLogLevel.Error, "Received a random pong");
                    CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace);
                }
                else
                {
                    _pongs.Dequeue().Pong();
                }
                break;

            case ServerCommand.Announce:
                CoreLog.Log(CoreLogLevel.Error, "Received a random server announcement");
                CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace);
                break;

            case ServerCommand.PublicCall:
                var name   = ValueConverter.ToString(data.Data, 0, Encoding);
                var isInit = name == "OnGameModeInit";

                if (CoreLog.DoesLog(CoreLogLevel.Verbose))
                {
                    CoreLog.LogVerbose("Incoming public call: {0}", name);
                }

                if ((_startBehaviour == GameModeStartBehaviour.Gmx || _startBehaviour == GameModeStartBehaviour.FakeGmx) && !_initReceived &&
                    !isInit)
                {
                    CoreLog.Log(CoreLogLevel.Debug, $"Skipping callback {name} because OnGameModeInit has not yet been called");
                    Send(ServerCommand.Response, AZero);
                    break;
                }

                var isFirstInit = isInit && !_initReceived;
                if (isFirstInit)
                {
                    _initReceived = true;
                }

                if (_callbacks.TryGetValue(name, out var callback))
                {
                    int?result = null;
                    try
                    {
                        result = callback.Invoke(data.Data, name.Length + 1);

                        CoreLog.LogVerbose("Public call response for {0}: {1}", name, result);
                    }
                    catch (Exception e)
                    {
                        OnUnhandledException(new UnhandledExceptionEventArgs(name, e));
                    }

                    Send(ServerCommand.Response,
                         result != null
                                ? AOne.Concat(ValueConverter.GetBytes(result.Value))
                                : AZero);
                }
                else
                {
                    CoreLog.Log(CoreLogLevel.Error, "Received unknown callback " + name);
                    CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace);
                }

                if (isFirstInit)
                {
                    if (_startBehaviour == GameModeStartBehaviour.FakeGmx)
                    {
                        FakeGmxRotate();
                    }

                    _canTick = true;
                }
                else if (_initReceived && name == "OnGameModeExit")
                {
                    CoreLog.Log(CoreLogLevel.Info, "OnGameModeExit received, sending reconnect signal...");
                    Send(ServerCommand.Reconnect, null);

                    // Give the server time to receive the reconnect signal.
                    Thread.Sleep(100);

                    CleanUp();
                }
                break;

            default:
                CoreLog.Log(CoreLogLevel.Error, $"Unknown command {data.Command} received with {data.Data?.Length.ToString() ?? "NULL"} data");
                CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace);
                break;
            }
        }
Beispiel #21
0
        private void ProcessCommand(ServerCommandData data)
        {
            switch (data.Command)
            {
            case ServerCommand.Tick:
                if (!_canTick)
                {
                    break;
                }

                _gameModeProvider.Tick();

                // The server expects at least a message every 5 seconds or else the debug pause
                // detector kicks in. Send one at least every 3 to be safe.
                if (DateTime.UtcNow - _lastSend > TimeSpan.FromSeconds(3))
                {
                    Send(ServerCommand.Alive, null);
                }

                break;

            case ServerCommand.Pong:
                if (_pongs.Count == 0)
                {
                    CoreLog.Log(CoreLogLevel.Error, "Received a random pong");
                    CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace);
                }
                else
                {
                    _pongs.Dequeue().Pong();
                }
                break;

            case ServerCommand.Announce:
                CoreLog.Log(CoreLogLevel.Error, "Received a random server announcement");
                CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace);
                break;

            case ServerCommand.PublicCall:
                var name   = ValueConverter.ToString(data.Data, 0, Encoding);
                var isInit = name == "OnGameModeInit";

                if (CoreLog.DoesLog(CoreLogLevel.Verbose))
                {
                    CoreLog.LogVerbose("Incoming public call: {0}", name);
                }

                if ((_startBehaviour == GameModeStartBehaviour.Gmx || _startBehaviour == GameModeStartBehaviour.FakeGmx) && !_initReceived &&
                    !isInit)
                {
                    CoreLog.Log(CoreLogLevel.Debug, $"Skipping callback {name} because OnGameModeInit has not yet been called");
                    Send(ServerCommand.Response, AZero);
                    break;
                }

                var isFirstInit = isInit && !_initReceived;
                if (isFirstInit)
                {
                    _initReceived = true;
                }

                if (_callbacks.TryGetValue(name, out var callback))
                {
                    int?result = null;
                    try
                    {
                        result = callback.Invoke(data.Data, name.Length + 1);

                        CoreLog.LogVerbose("Public call response for {0}: {1}", name, result);
                    }
                    catch (Exception e)
                    {
                        OnUnhandledException(new UnhandledExceptionEventArgs(e));
                    }

                    Send(ServerCommand.Response,
                         result != null
                                ? AOne.Concat(ValueConverter.GetBytes(result.Value))
                                : AZero);
                }
                else
                {
                    CoreLog.Log(CoreLogLevel.Error, "Received unknown callback " + name);
                    CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace);
                }

                if (isFirstInit)
                {
                    if (_startBehaviour == GameModeStartBehaviour.FakeGmx)
                    {
                        _callbacks.TryGetValue("OnIncomingConnection", out var onIncomingConnection);
                        _callbacks.TryGetValue("OnPlayerConnect", out var onPlayerConnect);
                        _callbacks.TryGetValue("OnPlayerRequestClass", out var onPlayerRequestClass);

                        var natIsPlayerConnected = NativeLoader.Load("IsPlayerConnected", new[]
                        {
                            NativeParameterInfo.ForType(typeof(int))
                        });
                        var natGetPlayerPoolSize   = NativeLoader.Load("GetPlayerPoolSize", new NativeParameterInfo[0]);
                        var natForceClassSelection =
                            NativeLoader.Load("ForceClassSelection", new[]
                        {
                            NativeParameterInfo.ForType(typeof(int))
                        });
                        var natTogglePlayerSpectating = NativeLoader.Load("TogglePlayerSpectating",
                                                                          new[]
                        {
                            NativeParameterInfo.ForType(typeof(int)),
                            NativeParameterInfo.ForType(typeof(int))
                        });
                        var natGetPlayerIp = NativeLoader.Load("GetPlayerIp",
                                                               new[]
                        {
                            NativeParameterInfo.ForType(typeof(int)),
                            new NativeParameterInfo(NativeParameterType.StringReference, 2),
                            NativeParameterInfo.ForType(typeof(int)),
                        });

                        var poolSize = natGetPlayerPoolSize.Invoke();
                        for (var i = 0; i <= poolSize; i++)
                        {
                            var isConnected = natIsPlayerConnected.Invoke(i);

                            if (isConnected == 0)
                            {
                                continue;
                            }

                            var args = new object[] { i, null, 16 };
                            natGetPlayerIp.Invoke(args);

                            if (args[1] is string ip)
                            {
                                onIncomingConnection?.Invoke(
                                    ValueConverter.GetBytes(i)
                                    .Concat(ValueConverter.GetBytes(ip, Encoding.ASCII))
                                    .Concat(ValueConverter.GetBytes(9999999))
                                    .ToArray(), 0);
                            }

                            onPlayerConnect?.Invoke(ValueConverter.GetBytes(i), 0);

                            natForceClassSelection.Invoke(i);
                            natTogglePlayerSpectating.Invoke(i, 1);
                            natTogglePlayerSpectating.Invoke(i, 0);

                            onPlayerRequestClass?.Invoke(
                                ValueConverter.GetBytes(i)
                                .Concat(ValueConverter.GetBytes(0))
                                .ToArray(), 0);
                        }
                    }

                    _canTick = true;
                }
                else if (_initReceived && name == "OnGameModeExit")
                {
                    CoreLog.Log(CoreLogLevel.Info, "OnGameModeExit received, sending reconnect signal...");
                    Send(ServerCommand.Reconnect, null);

                    // Give the server time to receive the reconnect signal.
                    // TODO: This is an ugly freeze/comms-deadlock fix.
                    Thread.Sleep(100);

                    CleanUp();
                }
                break;

            default:
                CoreLog.Log(CoreLogLevel.Error, $"Unknown command {data.Command} recieved with {data.Data?.Length.ToString() ?? "NULL"} data");
                CoreLog.Log(CoreLogLevel.Debug, Environment.StackTrace);
                break;
            }
        }