Ejemplo n.º 1
0
        void InterpretServerOrder(Connection conn, Order o)
        {
            lock (LobbyInfo)
            {
                // Only accept handshake responses from unvalidated clients
                // Anything else may be an attempt to exploit the server
                if (!conn.Validated)
                {
                    if (o.OrderString == "HandshakeResponse")
                    {
                        ValidateClient(conn, o.TargetString);
                    }
                    else
                    {
                        Log.Write("server", "Rejected connection from {0}; Order `{1}` is not a `HandshakeResponse`.",
                                  conn.Socket.RemoteEndPoint, o.OrderString);

                        DropClient(conn);
                    }

                    return;
                }

                switch (o.OrderString)
                {
                case "Command":
                {
                    var handledBy = serverTraits.WithInterface <IInterpretCommand>()
                                    .FirstOrDefault(t => t.InterpretCommand(this, conn, GetClient(conn), o.TargetString));

                    if (handledBy == null)
                    {
                        Log.Write("server", "Unknown server command: {0}", o.TargetString);
                        SendOrderTo(conn, "Message", "Unknown server command: {0}".F(o.TargetString));
                    }

                    break;
                }

                case "Chat":
                    DispatchOrdersToClients(conn, 0, o.Serialize());
                    break;

                case "Pong":
                {
                    if (!OpenRA.Exts.TryParseInt64Invariant(o.TargetString, out var pingSent))
                    {
                        Log.Write("server", "Invalid order pong payload: {0}", o.TargetString);
                        break;
                    }

                    var client = GetClient(conn);
                    if (client == null)
                    {
                        return;
                    }

                    var pingFromClient = LobbyInfo.PingFromClient(client);
                    if (pingFromClient == null)
                    {
                        return;
                    }

                    var history = pingFromClient.LatencyHistory.ToList();
                    history.Add(Game.RunTime - pingSent);

                    // Cap ping history at 5 values (25 seconds)
                    if (history.Count > 5)
                    {
                        history.RemoveRange(0, history.Count - 5);
                    }

                    pingFromClient.Latency        = history.Sum() / history.Count;
                    pingFromClient.LatencyJitter  = (history.Max() - history.Min()) / 2;
                    pingFromClient.LatencyHistory = history.ToArray();

                    SyncClientPing();

                    break;
                }

                case "GameSaveTraitData":
                {
                    if (GameSave != null)
                    {
                        var data = MiniYaml.FromString(o.TargetString)[0];
                        GameSave.AddTraitData(int.Parse(data.Key), data.Value);
                    }

                    break;
                }

                case "CreateGameSave":
                {
                    if (GameSave != null)
                    {
                        // Sanitize potentially malicious input
                        var filename     = o.TargetString;
                        var invalidIndex = -1;
                        var invalidChars = Path.GetInvalidFileNameChars();
                        while ((invalidIndex = filename.IndexOfAny(invalidChars)) != -1)
                        {
                            filename = filename.Remove(invalidIndex, 1);
                        }

                        var baseSavePath = Platform.ResolvePath(
                            Platform.SupportDirPrefix,
                            "Saves",
                            ModData.Manifest.Id,
                            ModData.Manifest.Metadata.Version);

                        if (!Directory.Exists(baseSavePath))
                        {
                            Directory.CreateDirectory(baseSavePath);
                        }

                        GameSave.Save(Path.Combine(baseSavePath, filename));
                        DispatchOrdersToClients(null, 0, Order.FromTargetString("GameSaved", filename, true).Serialize());
                    }

                    break;
                }

                case "LoadGameSave":
                {
                    if (Type == ServerType.Dedicated || State >= ServerState.GameStarted)
                    {
                        break;
                    }

                    // Sanitize potentially malicious input
                    var filename     = o.TargetString;
                    var invalidIndex = -1;
                    var invalidChars = Path.GetInvalidFileNameChars();
                    while ((invalidIndex = filename.IndexOfAny(invalidChars)) != -1)
                    {
                        filename = filename.Remove(invalidIndex, 1);
                    }

                    var savePath = Platform.ResolvePath(
                        Platform.SupportDirPrefix,
                        "Saves",
                        ModData.Manifest.Id,
                        ModData.Manifest.Metadata.Version,
                        filename);

                    GameSave = new GameSave(savePath);
                    LobbyInfo.GlobalSettings = GameSave.GlobalSettings;
                    LobbyInfo.Slots          = GameSave.Slots;

                    // Reassign clients to slots
                    //  - Bot ordering is preserved
                    //  - Humans are assigned on a first-come-first-serve basis
                    //  - Leftover humans become spectators

                    // Start by removing all bots and assigning all players as spectators
                    foreach (var c in LobbyInfo.Clients)
                    {
                        if (c.Bot != null)
                        {
                            LobbyInfo.Clients.Remove(c);
                            var ping = LobbyInfo.PingFromClient(c);
                            if (ping != null)
                            {
                                LobbyInfo.ClientPings.Remove(ping);
                            }
                        }
                        else
                        {
                            c.Slot = null;
                        }
                    }

                    // Rebuild/remap the saved client state
                    // TODO: Multiplayer saves should leave all humans as spectators so they can manually pick slots
                    var adminClientIndex = LobbyInfo.Clients.First(c => c.IsAdmin).Index;
                    foreach (var kv in GameSave.SlotClients)
                    {
                        if (kv.Value.Bot != null)
                        {
                            var bot = new Session.Client()
                            {
                                Index = ChooseFreePlayerIndex(),
                                State = Session.ClientState.NotReady,
                                BotControllerClientIndex = adminClientIndex
                            };

                            kv.Value.ApplyTo(bot);
                            LobbyInfo.Clients.Add(bot);
                        }
                        else
                        {
                            // This will throw if the server doesn't have enough human clients to fill all player slots
                            // See TODO above - this isn't a problem in practice because MP saves won't use this
                            var client = LobbyInfo.Clients.First(c => c.Slot == null);
                            kv.Value.ApplyTo(client);
                        }
                    }

                    SyncLobbyInfo();
                    SyncLobbyClients();
                    SyncClientPing();

                    break;
                }
                }
            }
        }