/// <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); }
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; } }
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; } }