diff --git a/OpenRA.Game/Network/GameSave.cs b/OpenRA.Game/Network/GameSave.cs index 00d34d02c1..33c650bf23 100644 --- a/OpenRA.Game/Network/GameSave.cs +++ b/OpenRA.Game/Network/GameSave.cs @@ -230,7 +230,7 @@ namespace OpenRA.Network foreach (var kv in TraitData) { var data = new List() { new MiniYamlNode(kv.Key.ToString(), kv.Value) }.WriteToString(); - packetFn(0, 0, new ServerOrder("SaveTraitData", data).Serialize()); + packetFn(0, 0, Order.FromTargetString("SaveTraitData", data, true).Serialize()); } ordersStream.Seek(0, SeekOrigin.Begin); diff --git a/OpenRA.Game/Network/Order.cs b/OpenRA.Game/Network/Order.cs index f57ef60c07..f8e3842d20 100644 --- a/OpenRA.Game/Network/Order.cs +++ b/OpenRA.Game/Network/Order.cs @@ -19,12 +19,14 @@ namespace OpenRA [Flags] enum OrderFields : byte { + None = 0x0, Target = 0x01, TargetString = 0x04, Queued = 0x08, ExtraLocation = 0x10, ExtraData = 0x20, - TargetIsCell = 0x40 + TargetIsCell = 0x40, + Subject = 0x80 } static class OrderFieldsExts @@ -72,12 +74,15 @@ namespace OpenRA case 0xFF: { var order = r.ReadString(); - var subjectId = r.ReadUInt32(); var flags = (OrderFields)r.ReadByte(); Actor subject = null; - if (world != null) - TryGetActorFromUInt(world, subjectId, out subject); + if (flags.HasField(OrderFields.Subject)) + { + var subjectId = r.ReadUInt32(); + if (world != null) + TryGetActorFromUInt(world, subjectId, out subject); + } var target = Target.Invalid; if (flags.HasField(OrderFields.Target)) @@ -139,22 +144,12 @@ namespace OpenRA if (world == null) return new Order(order, null, target, targetString, queued, extraLocation, extraData); - if (subject == null && subjectId != uint.MaxValue) + if (subject == null && flags.HasField(OrderFields.Subject)) return null; return new Order(order, subject, target, targetString, queued, extraLocation, extraData); } - case 0xfe: - { - var name = r.ReadString(); - var flags = (OrderFields)r.ReadByte(); - var targetString = flags.HasField(OrderFields.TargetString) ? r.ReadString() : null; - var extraData = flags.HasField(OrderFields.ExtraData) ? r.ReadUInt32() : 0; - - return new Order(name, null, false) { IsImmediate = true, TargetString = targetString, ExtraData = extraData }; - } - default: { Log.Write("debug", "Received unknown order with magic {0}", magic); @@ -200,19 +195,9 @@ namespace OpenRA return new Order("Chat", null, false) { IsImmediate = true, TargetString = text, ExtraData = teamNumber }; } - public static Order HandshakeResponse(string text) + public static Order FromTargetString(string order, string targetString, bool isImmediate) { - return new Order("HandshakeResponse", null, false) { IsImmediate = true, TargetString = text }; - } - - public static Order Pong(string pingTime) - { - return new Order("Pong", null, false) { IsImmediate = true, TargetString = pingTime }; - } - - public static Order PauseGame(bool paused) - { - return new Order("PauseGame", null, false) { TargetString = paused ? "Pause" : "UnPause" }; + return new Order(order, null, false) { IsImmediate = isImmediate, TargetString = targetString }; } public static Order Command(string text) @@ -247,36 +232,23 @@ namespace OpenRA public byte[] Serialize() { - var minLength = OrderString.Length + 1 + (IsImmediate ? 1 + 1 + TargetString.Length + 1 + 4 : 6); + var minLength = 1 + OrderString.Length + 1 + 4 + 1 + 13 + (TargetString != null ? TargetString.Length + 1 : 0) + 4 + 4; var ret = new MemoryStream(minLength); var w = new BinaryWriter(ret); - OrderFields fields = 0; + w.Write((byte)0xFF); + w.Write(OrderString); + + var fields = OrderFields.None; + if (Subject != null) + fields |= OrderFields.Subject; + if (TargetString != null) fields |= OrderFields.TargetString; if (ExtraData != 0) fields |= OrderFields.ExtraData; - if (IsImmediate) - { - w.Write((byte)0xFE); - w.Write(OrderString); - w.Write((byte)fields); - - if (fields.HasField(OrderFields.TargetString)) - w.Write(TargetString); - - if (fields.HasField(OrderFields.ExtraData)) - w.Write(ExtraData); - - return ret.ToArray(); - } - - w.Write((byte)0xFF); - w.Write(OrderString); - w.Write(UIntFromActor(Subject)); - if (Target.SerializableType != TargetType.Invalid) fields |= OrderFields.Target; @@ -291,6 +263,9 @@ namespace OpenRA w.Write((byte)fields); + if (fields.HasField(OrderFields.Subject)) + w.Write(UIntFromActor(Subject)); + if (fields.HasField(OrderFields.Target)) { w.Write((byte)Target.SerializableType); diff --git a/OpenRA.Game/Network/UnitOrders.cs b/OpenRA.Game/Network/UnitOrders.cs index 237ac7cccd..d7e0d4eda3 100644 --- a/OpenRA.Game/Network/UnitOrders.cs +++ b/OpenRA.Game/Network/UnitOrders.cs @@ -13,6 +13,7 @@ using System; using System.Collections.Generic; using System.Linq; using OpenRA.Primitives; +using OpenRA.Server; using OpenRA.Traits; namespace OpenRA.Network @@ -232,7 +233,12 @@ namespace OpenRA.Network if (request.AuthToken != null && response.Fingerprint != null) response.AuthSignature = localProfile.Sign(request.AuthToken); - orderManager.IssueOrder(Order.HandshakeResponse(response.Serialize())); + orderManager.IssueOrder(new Order("HandshakeResponse", null, false) + { + IsImmediate = true, + TargetString = response.Serialize() + }); + break; } @@ -326,19 +332,15 @@ namespace OpenRA.Network case "Ping": { - orderManager.IssueOrder(Order.Pong(order.TargetString)); + orderManager.IssueOrder(Order.FromTargetString("Pong", order.TargetString, true)); break; } default: { - if (!order.IsImmediate) - { - var self = order.Subject; - if (!self.IsDead) - foreach (var t in self.TraitsImplementing()) - t.ResolveOrder(self, order); - } + if (order.Subject != null && !order.Subject.IsDead) + foreach (var t in order.Subject.TraitsImplementing()) + t.ResolveOrder(order.Subject, order); break; } diff --git a/OpenRA.Game/Server/Server.cs b/OpenRA.Game/Server/Server.cs index ecb5279e22..29a7857d5a 100644 --- a/OpenRA.Game/Server/Server.cs +++ b/OpenRA.Game/Server/Server.cs @@ -292,7 +292,11 @@ namespace OpenRA.Server AuthToken = token }; - DispatchOrdersToClient(newConn, 0, 0, new ServerOrder("HandshakeRequest", request.Serialize()).Serialize()); + DispatchOrdersToClient(newConn, 0, 0, new Order("HandshakeRequest", null, false) + { + IsImmediate = true, + TargetString = request.Serialize() + }.Serialize()); } catch (Exception e) { @@ -579,9 +583,9 @@ namespace OpenRA.Server { while (ms.Position < ms.Length) { - var so = ServerOrder.Deserialize(br); - if (so == null) return; - InterpretServerOrder(conn, so); + var o = Order.Deserialize(null, br); + if (o != null) + InterpretServerOrder(conn, o); } } catch (EndOfStreamException) { } @@ -590,29 +594,29 @@ namespace OpenRA.Server public void SendOrderTo(Connection conn, string order, string data) { - DispatchOrdersToClient(conn, 0, 0, new ServerOrder(order, data).Serialize()); + DispatchOrdersToClient(conn, 0, 0, Order.FromTargetString(order, data, true).Serialize()); } public void SendMessage(string text, Connection conn = null) { - DispatchOrdersToClients(conn, 0, new ServerOrder("Message", text).Serialize()); + DispatchOrdersToClients(conn, 0, Order.FromTargetString("Message", text, true).Serialize()); if (Dedicated) Console.WriteLine("[{0}] {1}".F(DateTime.Now.ToString(Settings.TimestampFormat), text)); } - void InterpretServerOrder(Connection conn, ServerOrder so) + void InterpretServerOrder(Connection conn, Order o) { // Only accept handshake responses from unvalidated clients // Anything else may be an attempt to exploit the server if (!conn.Validated) { - if (so.Name == "HandshakeResponse") - ValidateClient(conn, so.Data); + 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, so.Name); + conn.Socket.RemoteEndPoint, o.OrderString); DropClient(conn); } @@ -620,31 +624,31 @@ namespace OpenRA.Server return; } - switch (so.Name) + switch (o.OrderString) { case "Command": { var handledBy = serverTraits.WithInterface() - .FirstOrDefault(t => t.InterpretCommand(this, conn, GetClient(conn), so.Data)); + .FirstOrDefault(t => t.InterpretCommand(this, conn, GetClient(conn), o.TargetString)); if (handledBy == null) { - Log.Write("server", "Unknown server command: {0}", so.Data); - SendOrderTo(conn, "Message", "Unknown server command: {0}".F(so.Data)); + 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, so.Serialize()); + DispatchOrdersToClients(conn, 0, o.Serialize()); break; case "Pong": { long pingSent; - if (!OpenRA.Exts.TryParseInt64Invariant(so.Data, out pingSent)) + if (!OpenRA.Exts.TryParseInt64Invariant(o.TargetString, out pingSent)) { - Log.Write("server", "Invalid order pong payload: {0}", so.Data); + Log.Write("server", "Invalid order pong payload: {0}", o.TargetString); break; } @@ -676,7 +680,7 @@ namespace OpenRA.Server { if (GameSave != null) { - var data = MiniYaml.FromString(so.Data)[0]; + var data = MiniYaml.FromString(o.TargetString)[0]; GameSave.AddTraitData(int.Parse(data.Key), data.Value); } @@ -688,7 +692,7 @@ namespace OpenRA.Server if (GameSave != null) { // Sanitize potentially malicious input - var filename = so.Data; + var filename = o.TargetString; var invalidIndex = -1; var invalidChars = Path.GetInvalidFileNameChars(); while ((invalidIndex = filename.IndexOfAny(invalidChars)) != -1) @@ -704,7 +708,7 @@ namespace OpenRA.Server Directory.CreateDirectory(baseSavePath); GameSave.Save(Path.Combine(baseSavePath, filename)); - DispatchOrdersToClients(null, 0, new ServerOrder("GameSaved", filename).Serialize()); + DispatchOrdersToClients(null, 0, Order.FromTargetString("GameSaved", filename, true).Serialize()); } break; @@ -716,7 +720,7 @@ namespace OpenRA.Server break; // Sanitize potentially malicious input - var filename = so.Data; + var filename = o.TargetString; var invalidIndex = -1; var invalidChars = Path.GetInvalidFileNameChars(); while ((invalidIndex = filename.IndexOfAny(invalidChars)) != -1) @@ -808,7 +812,7 @@ namespace OpenRA.Server SendMessage("{0}{1} has disconnected.".F(dropClient.Name, suffix)); // Send disconnected order, even if still in the lobby - DispatchOrdersToClients(toDrop, 0, new ServerOrder("Disconnected", "").Serialize()); + DispatchOrdersToClients(toDrop, 0, Order.FromTargetString("Disconnected", "", true).Serialize()); LobbyInfo.Clients.RemoveAll(c => c.Index == toDrop.PlayerIndex); LobbyInfo.ClientPings.RemoveAll(p => p.Index == toDrop.PlayerIndex); @@ -854,7 +858,7 @@ namespace OpenRA.Server public void SyncLobbyInfo() { if (State == ServerState.WaitingPlayers) // Don't do this while the game is running, it breaks things! - DispatchOrders(null, 0, new ServerOrder("SyncInfo", LobbyInfo.Serialize()).Serialize()); + DispatchOrders(null, 0, Order.FromTargetString("SyncInfo", LobbyInfo.Serialize(), true).Serialize()); foreach (var t in serverTraits.WithInterface()) t.LobbyInfoSynced(this); @@ -868,7 +872,7 @@ namespace OpenRA.Server // TODO: Only need to sync the specific client that has changed to avoid conflicts! var clientData = LobbyInfo.Clients.Select(client => client.Serialize()).ToList(); - DispatchOrders(null, 0, new ServerOrder("SyncLobbyClients", clientData.WriteToString()).Serialize()); + DispatchOrders(null, 0, Order.FromTargetString("SyncLobbyClients", clientData.WriteToString(), true).Serialize()); foreach (var t in serverTraits.WithInterface()) t.LobbyInfoSynced(this); @@ -882,7 +886,7 @@ namespace OpenRA.Server // TODO: Don't sync all the slots if just one changed! var slotData = LobbyInfo.Slots.Select(slot => slot.Value.Serialize()).ToList(); - DispatchOrders(null, 0, new ServerOrder("SyncLobbySlots", slotData.WriteToString()).Serialize()); + DispatchOrders(null, 0, Order.FromTargetString("SyncLobbySlots", slotData.WriteToString(), true).Serialize()); foreach (var t in serverTraits.WithInterface()) t.LobbyInfoSynced(this); @@ -895,7 +899,7 @@ namespace OpenRA.Server var sessionData = new List { LobbyInfo.GlobalSettings.Serialize() }; - DispatchOrders(null, 0, new ServerOrder("SyncLobbyGlobalSettings", sessionData.WriteToString()).Serialize()); + DispatchOrders(null, 0, Order.FromTargetString("SyncLobbyGlobalSettings", sessionData.WriteToString(), true).Serialize()); foreach (var t in serverTraits.WithInterface()) t.LobbyInfoSynced(this); @@ -907,7 +911,7 @@ namespace OpenRA.Server var clientPings = LobbyInfo.ClientPings.Select(ping => ping.Serialize()).ToList(); // Note that syncing pings doesn't trigger INotifySyncLobbyInfo - DispatchOrders(null, 0, new ServerOrder("SyncClientPings", clientPings.WriteToString()).Serialize()); + DispatchOrders(null, 0, Order.FromTargetString("SyncClientPings", clientPings.WriteToString(), true).Serialize()); } public void StartGame() @@ -960,7 +964,7 @@ namespace OpenRA.Server } DispatchOrders(null, 0, - new ServerOrder("StartGame", startGameData).Serialize()); + Order.FromTargetString("StartGame", startGameData, true).Serialize()); foreach (var t in serverTraits.WithInterface()) t.GameStarted(this); diff --git a/OpenRA.Game/Server/ServerOrder.cs b/OpenRA.Game/Server/ServerOrder.cs deleted file mode 100644 index 9441f667b2..0000000000 --- a/OpenRA.Game/Server/ServerOrder.cs +++ /dev/null @@ -1,82 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2019 The OpenRA Developers (see AUTHORS) - * This file is part of OpenRA, which is free software. It is made - * available to you under the terms of the GNU General Public License - * as published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. For more - * information, see COPYING. - */ -#endregion - -using System; -using System.IO; - -namespace OpenRA.Server -{ - class ServerOrder - { - public readonly string Name; - public readonly string Data; - public readonly uint ExtraData; - - public ServerOrder(string name, string data, uint extraData = 0) - { - Name = name; - Data = data; - ExtraData = extraData; - } - - public static ServerOrder Deserialize(BinaryReader r) - { - byte b; - switch (b = r.ReadByte()) - { - case 0xbf: - // Silently ignore disconnect notifications - return null; - case 0xff: - Console.WriteLine("This isn't a server order."); - return null; - - case 0xfe: - { - var name = r.ReadString(); - var flags = (OrderFields)r.ReadByte(); - var data = flags.HasField(OrderFields.TargetString) ? r.ReadString() : null; - var extraData = flags.HasField(OrderFields.ExtraData) ? r.ReadUInt32() : 0; - - return new ServerOrder(name, data, extraData); - } - - default: - throw new NotImplementedException(b.ToString("x2")); - } - } - - public byte[] Serialize() - { - var ms = new MemoryStream(1 + Name.Length + 1 + 1 + Data.Length + 1 + 4); - var bw = new BinaryWriter(ms); - - OrderFields fields = 0; - if (Data != null) - fields |= OrderFields.TargetString; - - if (ExtraData != 0) - fields |= OrderFields.ExtraData; - - bw.Write((byte)0xfe); - bw.Write(Name); - bw.Write((byte)fields); - - if (fields.HasField(OrderFields.TargetString)) - bw.Write(Data); - - if (fields.HasField(OrderFields.ExtraData)) - bw.Write(ExtraData); - - return ms.ToArray(); - } - } -} diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index c7f1619d9f..6091264957 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -135,7 +135,8 @@ namespace OpenRA readonly GameInformation gameInfo; - public void IssueOrder(Order o) { OrderManager.IssueOrder(o); } /* avoid exposing the OM to mod code */ + // Hide the OrderManager from mod code + public void IssueOrder(Order o) { OrderManager.IssueOrder(o); } IOrderGenerator orderGenerator; public IOrderGenerator OrderGenerator @@ -377,7 +378,7 @@ namespace OpenRA if (PauseStateLocked) return; - IssueOrder(Order.PauseGame(paused)); + IssueOrder(Order.FromTargetString("PauseGame", paused ? "Pause" : "UnPause", false)); PredictedPaused = paused; } @@ -523,21 +524,13 @@ namespace OpenRA if (data != null) { var yaml = new List() { new MiniYamlNode(i.ToString(), new MiniYaml("", data)) }; - IssueOrder(new Order("GameSaveTraitData", null, false) - { - IsImmediate = true, - TargetString = yaml.WriteToString() - }); + IssueOrder(Order.FromTargetString("GameSaveTraitData", yaml.WriteToString(), true)); } i++; } - IssueOrder(new Order("CreateGameSave", null, false) - { - IsImmediate = true, - TargetString = filename - }); + IssueOrder(Order.FromTargetString("CreateGameSave", filename, true)); } public bool Disposing; diff --git a/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs index 21252a5ff9..64e301c0e4 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs @@ -290,11 +290,7 @@ namespace OpenRA.Mods.Common.Widgets.Logic var orders = new List() { - new Order("LoadGameSave", null, false) - { - IsImmediate = true, - TargetString = Path.GetFileName(selectedSave) - }, + Order.FromTargetString("LoadGameSave", Path.GetFileName(selectedSave), true), Order.Command("state {0}".F(Session.ClientState.Ready)) };