private static MiniYaml FlipFacings(MiniYaml info) { var d = info.ToDictionary(); if (!LoadField(d, "FlipFacings", false)) { return(info); } var source = info.Value; info.Value = null; var frames = LoadField <int[]>(d, "Frames", null); info.Nodes.Remove(info.Nodes.First(node => node.Key == "Frames")); var combine = "Combine:\n"; for (var i = 0; i < frames.Length; i++) { combine += "\t" + source + ":\n\t\tStart:" + frames[i] + "\n\t\tAddExtension:false\n"; } for (var i = frames.Length - 2; i > 0; i--) { combine += "\t" + source + ":\n\t\tStart:" + frames[i] + "\n\t\tAddExtension:false\n\t\tFlipX:true\n"; } info.Nodes.Add(MiniYaml.FromString(combine)[0]); return(info); }
public void TestEscapedHashInValues() { var trailingWhitespace = MiniYaml.FromString(@"key: value # comment", "trailingWhitespace", discardCommentsAndWhitespace: false)[0]; Assert.AreEqual("value", trailingWhitespace.Value.Value); Assert.AreEqual(" comment", trailingWhitespace.Comment); var noWhitespace = MiniYaml.FromString(@"key:value# comment", "noWhitespace", discardCommentsAndWhitespace: false)[0]; Assert.AreEqual("value", noWhitespace.Value.Value); Assert.AreEqual(" comment", noWhitespace.Comment); var escapedHashInValue = MiniYaml.FromString(@"key: before \# after # comment", "escapedHashInValue", discardCommentsAndWhitespace: false)[0]; Assert.AreEqual("before # after", escapedHashInValue.Value.Value); Assert.AreEqual(" comment", escapedHashInValue.Comment); var emptyValueAndComment = MiniYaml.FromString(@"key:#", "emptyValueAndComment", discardCommentsAndWhitespace: false)[0]; Assert.AreEqual(null, emptyValueAndComment.Value.Value); Assert.AreEqual("", emptyValueAndComment.Comment); var noValue = MiniYaml.FromString(@"key:", "noValue", discardCommentsAndWhitespace: false)[0]; Assert.AreEqual(null, noValue.Value.Value); Assert.AreEqual(null, noValue.Comment); var emptyKey = MiniYaml.FromString(@" : value", "emptyKey", discardCommentsAndWhitespace: false)[0]; Assert.AreEqual(null, emptyKey.Key); Assert.AreEqual("value", emptyKey.Value.Value); Assert.AreEqual(null, emptyKey.Comment); }
public void TestSelfMerging() { var baseYaml = @" Test: Merge: original Child: original Original: Test: Merge: override Child: override Override: "; var result = MiniYaml.Merge(new[] { baseYaml }.Select(s => MiniYaml.FromString(s, ""))); Assert.That(result.Count(n => n.Key == "Test"), Is.EqualTo(1), "Result should have exactly one Test node."); var testNodes = result.First(n => n.Key == "Test").Value.Nodes; Assert.That(testNodes.Select(n => n.Key), Is.EqualTo(new[] { "Merge", "Original", "Override" }), "Merged Test node has incorrect child nodes."); var mergeNode = testNodes.First(n => n.Key == "Merge").Value; Assert.That(mergeNode.Value, Is.EqualTo("override"), "Merge node has incorrect value."); Assert.That(mergeNode.Nodes[0].Value.Value, Is.EqualTo("override"), "Merge node Child value should be 'override', but is not"); }
public void ChildCanBeOverriddenThenRemoved() { var baseYaml = @" ^BaseA: MockString: AString: Base ^BaseB: Inherits: ^BaseA MockString: AString: Override "; var overrideYaml = @" Test: Inherits: ^BaseB MockString: -AString: "; var result = MiniYaml.Merge(new[] { baseYaml, overrideYaml }.Select(s => MiniYaml.FromString(s, ""))) .First(n => n.Key == "Test").Value.Nodes; Assert.IsTrue(result.Any(n => n.Key == "MockString"), "Node should have the MockString child, but does not."); Assert.IsFalse(result.First(n => n.Key == "MockString").Value.Nodes.Any(n => n.Key == "AString"), "MockString value should have been removed, but was not."); }
void RefreshServerList() { // Query in progress if (currentQuery != null) { return; } searchStatus = SearchStatus.Fetching; Action <DownloadDataCompletedEventArgs, bool> onComplete = (i, cancelled) => { currentQuery = null; if (i.Error != null || cancelled) { RefreshServerListInner(null); return; } var data = Encoding.UTF8.GetString(i.Result); var yaml = MiniYaml.FromString(data); var games = yaml.Select(a => new GameServer(a.Value)) .Where(gs => gs.Address != null); Game.RunAfterTick(() => RefreshServerListInner(games)); }; currentQuery = new Download(Game.Settings.Server.MasterServer + "games", _ => { }, onComplete); }
public static HandshakeRequest Deserialize(string data) { var handshake = new HandshakeRequest(); FieldLoader.Load(handshake, MiniYaml.FromString(data).First().Value); return(handshake); }
public static Session Deserialize(string data) { var session = new Session(Game.Settings.Game.Mods); var ys = MiniYaml.FromString(data); foreach (var y in ys) { var yy = y.Key.Split('@'); switch (yy[0]) { case "GlobalSettings": FieldLoader.Load(session.GlobalSettings, y.Value); break; case "Client": session.Clients.Add(FieldLoader.Load <Session.Client>(y.Value)); break; case "Slot": var s = FieldLoader.Load <Session.Slot>(y.Value); session.Slots.Add(s.PlayerReference, s); break; } } return(session); }
public static HandshakeResponse Deserialize(string data) { var handshake = new HandshakeResponse { Client = new Session.Client() }; var ys = MiniYaml.FromString(data); foreach (var y in ys) { switch (y.Key) { case "Handshake": FieldLoader.Load(handshake, y.Value); break; case "Client": FieldLoader.Load(handshake.Client, y.Value); break; } } return(handshake); }
public void SetUp() { parentList = MiniYaml.FromString(yamlForParent); childList = MiniYaml.FromString(yamlForChild); parent = parentList.First().Value; child = childList.First().Value; }
public void TestGuardedWhitespace() { var testYaml = @"key: \ test value \ "; var nodes = MiniYaml.FromString(testYaml, "testYaml"); Assert.AreEqual(" test value ", nodes[0].Value.Value); }
public GameSave(string filepath) { using (var rs = File.OpenRead(filepath)) { rs.Seek(-12, SeekOrigin.End); var metadataOffset = rs.ReadInt32(); var traitDataOffset = rs.ReadInt32(); if (rs.ReadInt32() != EOFMarker) { throw new InvalidDataException("Invalid orasav file"); } rs.Seek(metadataOffset, SeekOrigin.Begin); if (rs.ReadInt32() != MetadataMarker) { throw new InvalidDataException("Invalid orasav file"); } LastOrdersFrame = rs.ReadInt32(); LastSyncFrame = rs.ReadInt32(); lastSyncPacket = rs.ReadBytes(Order.SyncHashOrderLength); var globalSettings = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength)); GlobalSettings = Session.Global.Deserialize(globalSettings[0].Value); var slots = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength)); Slots = new Dictionary <string, Session.Slot>(); foreach (var s in slots) { var slot = Session.Slot.Deserialize(s.Value); Slots.Add(slot.PlayerReference, slot); } var slotClients = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength)); SlotClients = new Dictionary <string, SlotClient>(); foreach (var s in slotClients) { var slotClient = SlotClient.Deserialize(s.Value); SlotClients.Add(slotClient.Slot, slotClient); } if (rs.Position != traitDataOffset || rs.ReadInt32() != TraitDataMarker) { throw new InvalidDataException("Invalid orasav file"); } var traitData = MiniYaml.FromString(rs.ReadString(Encoding.UTF8, Connection.MaxOrderLength)); foreach (var td in traitData) { TraitData.Add(int.Parse(td.Key), td.Value); } rs.Seek(0, SeekOrigin.Begin); ordersStream.Write(rs.ReadBytes(metadataOffset), 0, metadataOffset); } }
public void TestIndents() { var tabs = MiniYaml.FromString(yamlTabStyle, "yamlTabStyle").WriteToString(); Console.WriteLine(tabs); var mixed = MiniYaml.FromString(yamlMixedStyle, "yamlMixedStyle").WriteToString(); Console.WriteLine(mixed); Assert.That(tabs, Is.EqualTo(mixed)); }
public void MergeYamlA() { var a = MiniYaml.FromString(mixedMergeA, "mixedMergeA"); var b = MiniYaml.FromString(mixedMergeB, "mixedMergeB"); // Merge order should not matter // Note: All the Merge* variants are different plumbing over the same // internal logic. Testing only MergeStrict is sufficent. TestMixedMerge(MiniYaml.MergeStrict(a, b).First().Value); TestMixedMerge(MiniYaml.MergeStrict(b, a).First().Value); }
static IEnumerable <NewsItem> ReadNews(byte[] bytes) { var str = Encoding.UTF8.GetString(bytes); return(MiniYaml.FromString(str).Select(node => new NewsItem { Title = node.Value.NodesDict["Title"].Value, Author = node.Value.NodesDict["Author"].Value, DateTime = FieldLoader.GetValue <DateTime>("DateTime", node.Key), Content = node.Value.NodesDict["Content"].Value })); }
// This needs to match the logic used in RulesetCache.LoadYamlRules ActorInfo CreateActorInfoFromYaml(string name, string mapYaml, params string[] yamls) { var initialNodes = mapYaml == null ? new List <MiniYamlNode>() : MiniYaml.FromString(mapYaml); var yaml = yamls .Select(s => MiniYaml.FromString(s)) .Aggregate(initialNodes, MiniYaml.MergePartial); var allUnits = yaml.ToDictionary(node => node.Key, node => node.Value); var unit = allUnits[name]; var creator = new ObjectCreator(new[] { typeof(ActorInfoTest).Assembly }); return(new ActorInfo(creator, name, unit, allUnits)); }
public void CommentsSurviveRoundTrip() { var yaml = @" # Top level comment node Parent: # comment without value # Indented comment node First: value containing a \# character Second: value # node with inline comment ".Replace("\r\n", "\n"); var result = MiniYaml.FromString(yaml, discardCommentsAndWhitespace: false).WriteToString(); Assert.AreEqual(yaml, result); }
public void EmptyLinesShouldCountTowardLineNumbers() { var yaml = @" TestA: Nothing: TestB: Nothing: "; var result = MiniYaml.FromString(yaml).First(n => n.Key == "TestB"); Assert.AreEqual(5, result.Location.Line); }
public static Session Deserialize(string data) { try { var session = new Session(); var nodes = MiniYaml.FromString(data); foreach (var node in nodes) { var strings = node.Key.Split('@'); switch (strings[0]) { case "Client": session.Clients.Add(Client.Deserialize(node.Value)); break; case "ClientPing": session.ClientPings.Add(ClientPing.Deserialize(node.Value)); break; case "GlobalSettings": session.GlobalSettings = Global.Deserialize(node.Value); break; case "Slot": var s = Slot.Deserialize(node.Value); session.Slots.Add(s.PlayerReference, s); break; case "DisabledSpawnPoints": session.DisabledSpawnPoints = FieldLoader.GetValue <HashSet <int> >("DisabledSpawnPoints", node.Value.Value); break; } } return(session); } catch (YamlException) { throw new YamlException("Session deserialized invalid MiniYaml:\n{0}".F(data)); } catch (InvalidOperationException) { throw new YamlException("Session deserialized invalid MiniYaml:\n{0}".F(data)); } }
public void TestEscapedHashInValues() { var trailingWhitespace = @"key: value # comment"; Assert.AreEqual("value", MiniYaml.FromString(trailingWhitespace, "trailingWhitespace")[0].Value.Value); var noWhitespace = @"key:value# comment"; Assert.AreEqual("value", MiniYaml.FromString(noWhitespace, "noWhitespace")[0].Value.Value); var escapedHashInValue = @"key: before \# after # comment"; Assert.AreEqual("before # after", MiniYaml.FromString(escapedHashInValue, "escapedHashInValue")[0].Value.Value); var emptyValue = @"key:# comment"; Assert.AreEqual(null, MiniYaml.FromString(emptyValue, "emptyValue")[0].Value.Value); }
public void CommentsShouldntSurviveRoundTrip() { var yaml = @" # Top level comment node Parent: # comment without value # Indented comment node First: value containing a \# character Second: value # node with inline comment "; var strippedYaml = @"Parent: First: value containing a \# character Second: value" .Replace("\r\n", "\n"); var result = MiniYaml.FromString(yaml).WriteToString(); Assert.AreEqual(strippedYaml, result); }
public void ChildCanBeRemovedAfterMultipleInheritance() { var baseYaml = @" ^BaseA: MockA2: Test: Inherits: ^BaseA MockA2: "; var overrideYaml = @" Test: -MockA2 "; var result = MiniYaml.Merge(new[] { baseYaml, overrideYaml }.Select(s => MiniYaml.FromString(s, ""))) .First(n => n.Key == "Test").Value.Nodes; Assert.IsFalse(result.Any(n => n.Key == "MockA2"), "Node should not have the MockA2 child, but does."); }
void RefreshServerList() { // Query in progress if (currentQuery != null) { return; } searchStatus = SearchStatus.Fetching; Action <DownloadDataCompletedEventArgs> onComplete = i => { currentQuery = null; List <GameServer> games = null; if (i.Error == null) { try { var data = Encoding.UTF8.GetString(i.Result); var yaml = MiniYaml.FromString(data); games = yaml.Select(a => new GameServer(a.Value)) .Where(gs => gs.Address != null) .ToList(); } catch { searchStatus = SearchStatus.Failed; } } Game.RunAfterTick(() => RefreshServerListInner(games)); }; var queryURL = Game.Settings.Server.MasterServer + "games?version={0}&mod={1}&modversion={2}".F( Uri.EscapeUriString(Game.Mods["modchooser"].Metadata.Version), Uri.EscapeUriString(Game.ModData.Manifest.Id), Uri.EscapeUriString(Game.ModData.Manifest.Metadata.Version)); currentQuery = new Download(queryURL, _ => { }, onComplete); }
public static Session Deserialize(string data) { try { var session = new Session(); var nodes = MiniYaml.FromString(data); foreach (var node in nodes) { var strings = node.Key.Split('@'); switch (strings[0]) { case "Client": session.Clients.Add(Client.Deserialize(node.Value)); break; case "ClientPing": session.ClientPings.Add(ClientPing.Deserialize(node.Value)); break; case "GlobalSettings": session.GlobalSettings = Global.Deserialize(node.Value); break; case "Slot": var s = Slot.Deserialize(node.Value); session.Slots.Add(s.PlayerReference, s); break; } } return(session); } catch (InvalidOperationException) { Log.Write("exception", "Session deserialized invalid MiniYaml:\n{0}".F(data)); throw; } }
public void CommentsShouldCountTowardLineNumbers() { var yaml = @" TestA: Nothing: # Comment TestB: Nothing: "; var resultDiscard = MiniYaml.FromString(yaml); var resultDiscardLine = resultDiscard.First(n => n.Key == "TestB").Location.Line; Assert.That(resultDiscardLine, Is.EqualTo(6), "Node TestB should report its location as line 6, but is not (discarding comments)"); Assert.That(resultDiscard[1].Key, Is.EqualTo("TestB"), "Node TestB should be the second child of the root node, but is not (discarding comments)"); var resultKeep = MiniYaml.FromString(yaml, discardCommentsAndWhitespace: false); var resultKeepLine = resultKeep.First(n => n.Key == "TestB").Location.Line; Assert.That(resultKeepLine, Is.EqualTo(6), "Node TestB should report its location as line 6, but is not (parsing comments)"); Assert.That(resultKeep[4].Key, Is.EqualTo("TestB"), "Node TestB should be the fifth child of the root node, but is not (parsing comments)"); }
public void ChildCanBeRemovedAndLaterOverridden() { var baseYaml = @" ^BaseA: MockString: AString: Base Test: Inherits: ^BaseA -MockString: "; var overrideYaml = @" Test: MockString: AString: Override "; var result = MiniYaml.Merge(new[] { baseYaml, overrideYaml }.Select(s => MiniYaml.FromString(s, ""))) .First(n => n.Key == "Test").Value.Nodes; Assert.IsTrue(result.Any(n => n.Key == "MockString"), "Node should have the MockString child, but does not."); Assert.IsTrue(result.First(n => n.Key == "MockString").Value.ToDictionary()["AString"].Value == "Override", "MockString value has not been set with the correct override value for AString."); }
public static void Query(Action <GameServer[]> onComplete) { var masterServerUrl = Game.Settings.Server.MasterServer; new Thread(() => { GameServer[] games = null; try { var str = GetData(new Uri(masterServerUrl + "list.php")); var yaml = MiniYaml.FromString(str); games = yaml.Select(a => FieldLoader.Load <GameServer>(a.Value)) .Where(gs => gs.Address != null).ToArray(); } catch { } Game.RunAfterTick(() => onComplete(games)); }) { IsBackground = true }.Start(); }
public static Session Deserialize(string data) { try { var session = new Session(Game.Settings.Game.Mods); var ys = MiniYaml.FromString(data); foreach (var y in ys) { var yy = y.Key.Split('@'); switch (yy[0]) { case "GlobalSettings": FieldLoader.Load(session.GlobalSettings, y.Value); break; case "Client": session.Clients.Add(FieldLoader.Load <Session.Client>(y.Value)); break; case "Slot": var s = FieldLoader.Load <Session.Slot>(y.Value); session.Slots.Add(s.PlayerReference, s); break; } } return(session); } catch (InvalidOperationException) { Log.Write("exception", "Session deserialized invalid MiniYaml:\n{0}".F(data)); throw; } }
public RegisteredProfileTooltipLogic(Widget widget, WorldRenderer worldRenderer, ModData modData, Session.Client client) { playerDatabase = modData.Manifest.Get <PlayerDatabase>(); var header = widget.Get("HEADER"); var badgeContainer = widget.Get("BADGES_CONTAINER"); var badgeSeparator = badgeContainer.GetOrNull("SEPARATOR"); var profileHeader = header.Get("PROFILE_HEADER"); var messageHeader = header.Get("MESSAGE_HEADER"); var message = messageHeader.Get <LabelWidget>("MESSAGE"); var messageFont = Game.Renderer.Fonts[message.Font]; profileHeader.IsVisible = () => profileLoaded; messageHeader.IsVisible = () => !profileLoaded; var profileWidth = 0; var maxProfileWidth = widget.Bounds.Width; var messageText = "Loading player profile..."; var messageWidth = messageFont.Measure(messageText).X + 2 * message.Bounds.Left; Task.Run(async() => { try { var httpClient = HttpClientFactory.Create(); var httpResponseMessage = await httpClient.GetAsync(playerDatabase.Profile + client.Fingerprint); var result = await httpResponseMessage.Content.ReadAsStringAsync(); var yaml = MiniYaml.FromString(result).First(); if (yaml.Key == "Player") { profile = FieldLoader.Load <PlayerProfile>(yaml.Value); Game.RunAfterTick(() => { var nameLabel = profileHeader.Get <LabelWidget>("PROFILE_NAME"); var nameFont = Game.Renderer.Fonts[nameLabel.Font]; var rankLabel = profileHeader.Get <LabelWidget>("PROFILE_RANK"); var rankFont = Game.Renderer.Fonts[rankLabel.Font]; var adminContainer = profileHeader.Get("GAME_ADMIN"); var adminLabel = adminContainer.Get <LabelWidget>("LABEL"); var adminFont = Game.Renderer.Fonts[adminLabel.Font]; var headerSizeOffset = profileHeader.Bounds.Height - messageHeader.Bounds.Height; nameLabel.GetText = () => profile.ProfileName; rankLabel.GetText = () => profile.ProfileRank; profileWidth = Math.Max(profileWidth, nameFont.Measure(profile.ProfileName).X + 2 * nameLabel.Bounds.Left); profileWidth = Math.Max(profileWidth, rankFont.Measure(profile.ProfileRank).X + 2 * rankLabel.Bounds.Left); header.Bounds.Height += headerSizeOffset; badgeContainer.Bounds.Y += header.Bounds.Height; if (client.IsAdmin) { profileWidth = Math.Max(profileWidth, adminFont.Measure(adminLabel.Text).X + 2 * adminLabel.Bounds.Left); adminContainer.IsVisible = () => true; profileHeader.Bounds.Height += adminLabel.Bounds.Height; header.Bounds.Height += adminLabel.Bounds.Height; badgeContainer.Bounds.Y += adminLabel.Bounds.Height; } Func <int, int> negotiateWidth = badgeWidth => { profileWidth = Math.Min(Math.Max(badgeWidth, profileWidth), maxProfileWidth); return(profileWidth); }; if (profile.Badges.Any()) { var badges = Ui.LoadWidget("PLAYER_PROFILE_BADGES_INSERT", badgeContainer, new WidgetArgs() { { "worldRenderer", worldRenderer }, { "profile", profile }, { "negotiateWidth", negotiateWidth } }); if (badges.Bounds.Height > 0) { badgeContainer.Bounds.Height = badges.Bounds.Height; badgeContainer.IsVisible = () => true; } } profileWidth = Math.Min(profileWidth, maxProfileWidth); header.Bounds.Width = widget.Bounds.Width = badgeContainer.Bounds.Width = profileWidth; widget.Bounds.Height = header.Bounds.Height + badgeContainer.Bounds.Height; if (badgeSeparator != null) { badgeSeparator.Bounds.Width = profileWidth - 2 * badgeSeparator.Bounds.X; } profileLoaded = true; }); } } catch (Exception e) { Log.Write("debug", "Failed to parse player data result with exception: {0}", e); } finally { if (profile == null) { messageText = "Failed to load player profile."; messageWidth = messageFont.Measure(messageText).X + 2 * message.Bounds.Left; header.Bounds.Width = widget.Bounds.Width = messageWidth; } } }); message.GetText = () => messageText; header.Bounds.Height += messageHeader.Bounds.Height; header.Bounds.Width = widget.Bounds.Width = messageWidth; widget.Bounds.Height = header.Bounds.Height; badgeContainer.Visible = false; }
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; } } } }
void ValidateClient(Connection newConn, string data) { try { if (State == ServerState.GameStarted) { Log.Write("server", "Rejected connection from {0}; game is already started.", newConn.Socket.RemoteEndPoint); SendOrderTo(newConn, "ServerError", "The game has already started"); DropClient(newConn); return; } var handshake = HandshakeResponse.Deserialize(data); if (!string.IsNullOrEmpty(Settings.Password) && handshake.Password != Settings.Password) { var message = string.IsNullOrEmpty(handshake.Password) ? "Server requires a password" : "Incorrect password"; SendOrderTo(newConn, "AuthenticationError", message); DropClient(newConn); return; } var ipAddress = ((IPEndPoint)newConn.Socket.RemoteEndPoint).Address; var client = new Session.Client { Name = OpenRA.Settings.SanitizedPlayerName(handshake.Client.Name), IPAddress = ipAddress.ToString(), AnonymizedIPAddress = Type != ServerType.Local && Settings.ShareAnonymizedIPs ? Session.AnonymizeIP(ipAddress) : null, Location = GeoIP.LookupCountry(ipAddress), Index = newConn.PlayerIndex, PreferredColor = handshake.Client.PreferredColor, Color = handshake.Client.Color, Faction = "Random", SpawnPoint = 0, Team = 0, State = Session.ClientState.Invalid, }; if (ModData.Manifest.Id != handshake.Mod) { Log.Write("server", "Rejected connection from {0}; mods do not match.", newConn.Socket.RemoteEndPoint); SendOrderTo(newConn, "ServerError", "Server is running an incompatible mod"); DropClient(newConn); return; } if (ModData.Manifest.Metadata.Version != handshake.Version) { Log.Write("server", "Rejected connection from {0}; Not running the same version.", newConn.Socket.RemoteEndPoint); SendOrderTo(newConn, "ServerError", "Server is running an incompatible version"); DropClient(newConn); return; } if (handshake.OrdersProtocol != ProtocolVersion.Orders) { Log.Write("server", "Rejected connection from {0}; incompatible Orders protocol version {1}.", newConn.Socket.RemoteEndPoint, handshake.OrdersProtocol); SendOrderTo(newConn, "ServerError", "Server is running an incompatible protocol"); DropClient(newConn); return; } // Check if IP is banned var bans = Settings.Ban.Union(TempBans); if (bans.Contains(client.IPAddress)) { Log.Write("server", "Rejected connection from {0}; Banned.", newConn.Socket.RemoteEndPoint); SendOrderTo(newConn, "ServerError", "You have been {0} from the server".F(Settings.Ban.Contains(client.IPAddress) ? "banned" : "temporarily banned")); DropClient(newConn); return; } Action completeConnection = () => { lock (LobbyInfo) { client.Slot = LobbyInfo.FirstEmptySlot(); client.IsAdmin = !LobbyInfo.Clients.Any(c1 => c1.IsAdmin); if (client.IsObserver && !LobbyInfo.GlobalSettings.AllowSpectators) { SendOrderTo(newConn, "ServerError", "The game is full"); DropClient(newConn); return; } if (client.Slot != null) { SyncClientToPlayerReference(client, Map.Players.Players[client.Slot]); } else { client.Color = Color.White; } // Promote connection to a valid client PreConns.Remove(newConn); Conns.Add(newConn); LobbyInfo.Clients.Add(client); newConn.Validated = true; var clientPing = new Session.ClientPing { Index = client.Index }; LobbyInfo.ClientPings.Add(clientPing); Log.Write("server", "Client {0}: Accepted connection from {1}.", newConn.PlayerIndex, newConn.Socket.RemoteEndPoint); if (client.Fingerprint != null) { Log.Write("server", "Client {0}: Player fingerprint is {1}.", newConn.PlayerIndex, client.Fingerprint); } foreach (var t in serverTraits.WithInterface <IClientJoined>()) { t.ClientJoined(this, newConn); } SyncLobbyInfo(); Log.Write("server", "{0} ({1}) has joined the game.", client.Name, newConn.Socket.RemoteEndPoint); // Report to all other players SendMessage("{0} has joined the game.".F(client.Name), newConn); // Send initial ping SendOrderTo(newConn, "Ping", Game.RunTime.ToString(CultureInfo.InvariantCulture)); if (Type == ServerType.Dedicated) { var motdFile = Platform.ResolvePath(Platform.SupportDirPrefix, "motd.txt"); if (!File.Exists(motdFile)) { File.WriteAllText(motdFile, "Welcome, have fun and good luck!"); } var motd = File.ReadAllText(motdFile); if (!string.IsNullOrEmpty(motd)) { SendOrderTo(newConn, "Message", motd); } } if (Map.DefinesUnsafeCustomRules) { SendOrderTo(newConn, "Message", "This map contains custom rules. Game experience may change."); } if (!LobbyInfo.GlobalSettings.EnableSingleplayer) { SendOrderTo(newConn, "Message", TwoHumansRequiredText); } else if (Map.Players.Players.Where(p => p.Value.Playable).All(p => !p.Value.AllowBots)) { SendOrderTo(newConn, "Message", "Bots have been disabled on this map."); } } }; if (Type == ServerType.Local) { // Local servers can only be joined by the local client, so we can trust their identity without validation client.Fingerprint = handshake.Fingerprint; completeConnection(); } else if (!string.IsNullOrEmpty(handshake.Fingerprint) && !string.IsNullOrEmpty(handshake.AuthSignature)) { waitingForAuthenticationCallback++; Action <DownloadDataCompletedEventArgs> onQueryComplete = i => { PlayerProfile profile = null; if (i.Error == null) { try { var yaml = MiniYaml.FromString(Encoding.UTF8.GetString(i.Result)).First(); if (yaml.Key == "Player") { profile = FieldLoader.Load <PlayerProfile>(yaml.Value); var publicKey = Encoding.ASCII.GetString(Convert.FromBase64String(profile.PublicKey)); var parameters = CryptoUtil.DecodePEMPublicKey(publicKey); if (!profile.KeyRevoked && CryptoUtil.VerifySignature(parameters, newConn.AuthToken, handshake.AuthSignature)) { client.Fingerprint = handshake.Fingerprint; Log.Write("server", "{0} authenticated as {1} (UID {2})", newConn.Socket.RemoteEndPoint, profile.ProfileName, profile.ProfileID); } else if (profile.KeyRevoked) { profile = null; Log.Write("server", "{0} failed to authenticate as {1} (key revoked)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint); } else { profile = null; Log.Write("server", "{0} failed to authenticate as {1} (signature verification failed)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint); } } else { Log.Write("server", "{0} failed to authenticate as {1} (invalid server response: `{2}` is not `Player`)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint, yaml.Key); } } catch (Exception ex) { Log.Write("server", "{0} failed to authenticate as {1} (exception occurred)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint); Log.Write("server", ex.ToString()); } } else { Log.Write("server", "{0} failed to authenticate as {1} (server error: `{2}`)", newConn.Socket.RemoteEndPoint, handshake.Fingerprint, i.Error); } delayedActions.Add(() => { var notAuthenticated = Type == ServerType.Dedicated && profile == null && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any()); var blacklisted = Type == ServerType.Dedicated && profile != null && Settings.ProfileIDBlacklist.Contains(profile.ProfileID); var notWhitelisted = Type == ServerType.Dedicated && Settings.ProfileIDWhitelist.Any() && (profile == null || !Settings.ProfileIDWhitelist.Contains(profile.ProfileID)); if (notAuthenticated) { Log.Write("server", "Rejected connection from {0}; Not authenticated.", newConn.Socket.RemoteEndPoint); SendOrderTo(newConn, "ServerError", "Server requires players to have an OpenRA forum account"); DropClient(newConn); } else if (blacklisted || notWhitelisted) { if (blacklisted) { Log.Write("server", "Rejected connection from {0}; In server blacklist.", newConn.Socket.RemoteEndPoint); } else { Log.Write("server", "Rejected connection from {0}; Not in server whitelist.", newConn.Socket.RemoteEndPoint); } SendOrderTo(newConn, "ServerError", "You do not have permission to join this server"); DropClient(newConn); } else { completeConnection(); } waitingForAuthenticationCallback--; }, 0); }; new Download(playerDatabase.Profile + handshake.Fingerprint, _ => { }, onQueryComplete); } else { if (Type == ServerType.Dedicated && (Settings.RequireAuthentication || Settings.ProfileIDWhitelist.Any())) { Log.Write("server", "Rejected connection from {0}; Not authenticated.", newConn.Socket.RemoteEndPoint); SendOrderTo(newConn, "ServerError", "Server requires players to have an OpenRA forum account"); DropClient(newConn); } else { completeConnection(); } } } catch (Exception ex) { Log.Write("server", "Dropping connection {0} because an error occurred:", newConn.Socket.RemoteEndPoint); Log.Write("server", ex.ToString()); DropClient(newConn); } }