From 30c145452233d78a0b09c645f8698829ce11a237 Mon Sep 17 00:00:00 2001 From: Ray Koopa Date: Tue, 25 Dec 2018 22:12:37 +0100 Subject: [PATCH] Add common server logging. --- src/Syroot.OnlineWorms.Server/Client.cs | 17 +++- src/Syroot.OnlineWorms.Server/Log.cs | 40 ++++++++ .../Net/GameConnection.cs | 27 ++++-- src/Syroot.OnlineWorms.Server/Net/Packet.cs | 95 ++++++++++++++++++- .../Net/PacketAttribute.cs | 11 +++ .../Net/PacketFactory.cs | 16 ++-- .../Net/PacketStream.cs | 7 +- .../Net/Packets/ConnectReplyPacket.cs | 11 ++- .../Net/Packets/RawQueryPacket.cs | 1 - src/Syroot.OnlineWorms.Server/Server.cs | 23 +++-- .../Syroot.Worms.OnlineWorms.Server.csproj | 3 - 11 files changed, 212 insertions(+), 39 deletions(-) create mode 100644 src/Syroot.OnlineWorms.Server/Log.cs diff --git a/src/Syroot.OnlineWorms.Server/Client.cs b/src/Syroot.OnlineWorms.Server/Client.cs index 7164de3..95fac0d 100644 --- a/src/Syroot.OnlineWorms.Server/Client.cs +++ b/src/Syroot.OnlineWorms.Server/Client.cs @@ -26,14 +26,27 @@ namespace Syroot.Worms.OnlineWorms.Server _server = server; } + // ---- METHODS (PROTECTED) ------------------------------------------------------------------------------------ + + protected override void OnPrePacketHandle(Packet packet) + { + _server.Log.Write(LogCategory.Client, $"{TcpClient.Client.RemoteEndPoint} >> {packet}"); + } + + protected override void OnPrePacketSend(Packet packet) + { + _server.Log.Write(LogCategory.Server, $"{TcpClient.Client.RemoteEndPoint} << {packet}"); + } + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- private Packet HandleConnect(ConnectQueryPacket connectPacket) { return new ConnectReplyPacket { - Unknown = "Online Worms Private Server", - Version = "114" + Unknown = _server.Name, + Unknown2 = _server.RegionName, + Version = _server.Version }; } diff --git a/src/Syroot.OnlineWorms.Server/Log.cs b/src/Syroot.OnlineWorms.Server/Log.cs new file mode 100644 index 0000000..3e28946 --- /dev/null +++ b/src/Syroot.OnlineWorms.Server/Log.cs @@ -0,0 +1,40 @@ +using System; + +namespace Syroot.Worms.OnlineWorms.Server +{ + /// + /// Represents simplistic textual logging. + /// + internal class Log + { + // ---- FIELDS ------------------------------------------------------------------------------------------------- + + private readonly object _lock = new object(); + + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- + + internal void Write(LogCategory category, string text) => Write((ConsoleColor)category, text); + + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- + + private void Write(ConsoleColor color, string text) + { + lock (_lock) + { + ConsoleColor prevColor = Console.ForegroundColor; + Console.ForegroundColor = color; + Console.WriteLine(text); + Console.ForegroundColor = prevColor; + } + } + } + + internal enum LogCategory + { + Info = ConsoleColor.White, + Connect = ConsoleColor.Cyan, + Disconnect = ConsoleColor.Magenta, + Client = ConsoleColor.DarkCyan, + Server = ConsoleColor.DarkMagenta + } +} diff --git a/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs b/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs index ef96c4f..09886f1 100644 --- a/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs +++ b/src/Syroot.OnlineWorms.Server/Net/GameConnection.cs @@ -23,7 +23,6 @@ namespace Syroot.Worms.OnlineWorms.Server.Net = new Dictionary>(); private readonly Dictionary _handlers; - private readonly TcpClient _tcpClient; private readonly byte[] _receiveBuffer; private readonly byte[] _sendDataBuffer; private readonly PacketStream _receiveStream; @@ -36,19 +35,25 @@ namespace Syroot.Worms.OnlineWorms.Server.Net /// Initializes a new instance of the class, handling the given /// . /// - /// The to communicate with. + /// The to communicate with. internal GameConnection(TcpClient tcpClient) { - _tcpClient = tcpClient; - Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} connected"); + TcpClient = tcpClient; _handlers = GetHandlers(); _receiveBuffer = new byte[_maxDataSize]; - _receiveBuffer = new byte[_maxDataSize]; - _receiveStream = new PacketStream(_tcpClient.GetStream()); + _sendDataBuffer = new byte[_maxDataSize]; + _receiveStream = new PacketStream(TcpClient.GetStream()); _sendStream = new PacketStream(new MemoryStream(_sendDataBuffer, 0, _maxDataSize, true)); } + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + /// + /// Gets or sets the with which the connection communicates. + /// + internal TcpClient TcpClient { get; } + // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- public void Dispose() @@ -77,7 +82,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net { if (disposing) { - _tcpClient.Dispose(); + TcpClient.Dispose(); _sendStream.Dispose(); } @@ -85,6 +90,10 @@ namespace Syroot.Worms.OnlineWorms.Server.Net } } + protected virtual void OnPrePacketHandle(Packet packet) { } + + protected virtual void OnPrePacketSend(Packet packet) { } + // ---- METHODS (PRIVATE) -------------------------------------------------------------------------------------- private Dictionary GetHandlers() @@ -107,13 +116,13 @@ namespace Syroot.Worms.OnlineWorms.Server.Net private void HandlePacket(Packet inPacket) { + OnPrePacketHandle(inPacket); Type packetType = inPacket.GetType(); - Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} >> {packetType.Name}"); // Invoke the handler and send back any packet resulting from it. if (_handlers[packetType].Invoke(this, new[] { inPacket }) is Packet outPacket) { - Console.WriteLine($"{_tcpClient.Client.RemoteEndPoint} << {outPacket.GetType().Name}"); + OnPrePacketSend(outPacket); SendPacket(outPacket); } } diff --git a/src/Syroot.OnlineWorms.Server/Net/Packet.cs b/src/Syroot.OnlineWorms.Server/Net/Packet.cs index ad648d1..d28c03b 100644 --- a/src/Syroot.OnlineWorms.Server/Net/Packet.cs +++ b/src/Syroot.OnlineWorms.Server/Net/Packet.cs @@ -1,4 +1,10 @@ -namespace Syroot.Worms.OnlineWorms.Server.Net +using System; +using System.Collections; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Syroot.Worms.OnlineWorms.Server.Net { /// /// Represents a packet with an ID specifying its contents. To allow the server to instantiate child packet classes, @@ -6,6 +12,93 @@ /// internal abstract class Packet { + // ---- METHODS (PUBLIC) --------------------------------------------------------------------------------------- + + public override string ToString() + { +#if DEBUG + string dump(object obj, int indentLevel) + { + StringBuilder sb = new StringBuilder(); + string indent = new string(' ', indentLevel * 4); + switch (obj) + { + case null: + sb.Append(indent); + sb.Append("null"); + break; + case String stringValue: + sb.Append(indent); + sb.Append('"'); + sb.Append(stringValue); + sb.Append('"'); + break; + case Byte[] byteArrayValue: + sb.Append(indent); + sb.Append(String.Join(" ", byteArrayValue.Select(x => x.ToString("X2")))); + break; + case IEnumerable iEnumerableValue: + sb.Append(indent); + sb.Append("[ "); + foreach (object element in iEnumerableValue) + sb.Append(dump(element, indentLevel)).Append(" "); + sb.Append("]"); + break; + case Byte byteValue: + sb.Append(indent); + sb.Append("0x" + byteValue.ToString("X2")); + break; + case Int16 int16Value: + sb.Append(indent); + sb.Append("0x" + int16Value.ToString("X4")); + break; + case Int32 int32Value: + sb.Append(indent); + sb.Append("0x" + int32Value.ToString("X8")); + break; + case Int64 int64Value: + sb.Append(indent); + sb.Append("0x" + int64Value.ToString("X16")); + break; + case UInt16 uint16Value: + sb.Append(indent); + sb.Append("0x" + uint16Value.ToString("X4")); + break; + case UInt32 uint32Value: + sb.Append(indent); + sb.Append("0x" + uint32Value.ToString("X8")); + break; + case UInt64 uint64Value: + sb.Append(indent); + sb.Append("0x" + uint64Value.ToString("X16")); + break; + case Enum enumValue: + sb.Append(indent); + sb.Append(enumValue.ToString()); + break; + default: + foreach (PropertyInfo property in obj.GetType().GetProperties( + BindingFlags.Instance | BindingFlags.NonPublic)) + { + // Ignore indexers. + if (property.GetIndexParameters().Length > 0) + continue; + sb.AppendLine(); + sb.Append((indent + property.Name).PadRight(20)); + sb.Append(" "); + sb.Append(dump(property.GetValue(obj), indentLevel + 1)); + } + sb.AppendLine(); + break; + } + return sb.ToString().TrimEnd(); + } + return String.Concat(GetType().Name, dump(this, 1)); +#else + return GetType().Name; +#endif + } + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- internal abstract void Deserialize(PacketStream stream); diff --git a/src/Syroot.OnlineWorms.Server/Net/PacketAttribute.cs b/src/Syroot.OnlineWorms.Server/Net/PacketAttribute.cs index 04c423c..0785227 100644 --- a/src/Syroot.OnlineWorms.Server/Net/PacketAttribute.cs +++ b/src/Syroot.OnlineWorms.Server/Net/PacketAttribute.cs @@ -2,11 +2,19 @@ namespace Syroot.Worms.OnlineWorms.Server.Net { + /// + /// Decorates a child class with the ID of the packet it represents. + /// [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] internal class PacketAttribute : Attribute { // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ + /// + /// Initializes a new instance of the class, decorating a class with the ID of the + /// packet it represents. + /// + /// The ID of the packet which the decorated class represents. public PacketAttribute(ushort id) { ID = id; @@ -14,6 +22,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net // ---- PROPERTIES --------------------------------------------------------------------------------------------- + /// + /// Gets the ID of the packet the decorated class represents. + /// public ushort ID { get; } } } diff --git a/src/Syroot.OnlineWorms.Server/Net/PacketFactory.cs b/src/Syroot.OnlineWorms.Server/Net/PacketFactory.cs index 9afd7e1..bb6d3ce 100644 --- a/src/Syroot.OnlineWorms.Server/Net/PacketFactory.cs +++ b/src/Syroot.OnlineWorms.Server/Net/PacketFactory.cs @@ -12,7 +12,7 @@ namespace Syroot.Worms.OnlineWorms.Server.Net { // ---- FIELDS ------------------------------------------------------------------------------------------------- - private static readonly Dictionary _packetTypeMap = new Dictionary(); + private static readonly Dictionary _packetTypes = new Dictionary(); // ---- CONSTRUCTORS & DESTRUCTOR ------------------------------------------------------------------------------ @@ -23,9 +23,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net { foreach (PacketAttribute packetAttribute in type.GetCustomAttributes()) { - if (_packetTypeMap.ContainsKey(packetAttribute.ID)) + if (_packetTypes.ContainsKey(packetAttribute.ID)) throw new InvalidOperationException($"Packet {packetAttribute.ID} mapped to multiple classes."); - _packetTypeMap.Add(packetAttribute.ID, type); + _packetTypes.Add(packetAttribute.ID, type); } } } @@ -41,27 +41,27 @@ namespace Syroot.Worms.OnlineWorms.Server.Net internal static Packet Create(ushort id) { #if DEBUG - return _packetTypeMap.TryGetValue(id, out Type type) + return _packetTypes.TryGetValue(id, out Type type) ? (Packet)Activator.CreateInstance(type, true) : new RawQueryPacket(id); #else - return (Packet)Activator.CreateInstance(_packetTypeMap[id], true); + return (Packet)Activator.CreateInstance(_packetTypes[id], true); #endif } /// /// Gets the ID for the class of the given . /// - /// The type of the . /// The packet, whose class ID will be returned. /// The ID of the class. - internal static ushort GetID(T packet) where T : Packet + internal static ushort GetID(Packet packet) { #if DEBUG if (packet is RawQueryPacket rawPacket) return rawPacket.ID; + else #endif - return _packetTypeMap.Where(x => x.Value == packet.GetType()).First().Key; + return _packetTypes.Where(x => x.Value == packet.GetType()).First().Key; } } } diff --git a/src/Syroot.OnlineWorms.Server/Net/PacketStream.cs b/src/Syroot.OnlineWorms.Server/Net/PacketStream.cs index fd7e0d7..a2a23d6 100644 --- a/src/Syroot.OnlineWorms.Server/Net/PacketStream.cs +++ b/src/Syroot.OnlineWorms.Server/Net/PacketStream.cs @@ -90,17 +90,12 @@ namespace Syroot.Worms.OnlineWorms.Server.Net internal string ReadString() { - // Strings are word prefixed and 0 termianted. - string value = _baseStream.ReadString(StringCoding.Int16CharCount); - _baseStream.Seek(1); - return value; + return _baseStream.ReadString(StringCoding.Int16CharCount); } internal void WriteString(string value) { - // Strings are word prefixed and 0 termianted. _baseStream.WriteString(value, StringCoding.Int16CharCount, _win949Encoding); - _baseStream.WriteByte(0); } } } diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs index 066d6a4..7fe33a4 100644 --- a/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs +++ b/src/Syroot.OnlineWorms.Server/Net/Packets/ConnectReplyPacket.cs @@ -1,4 +1,5 @@ using System; +using Syroot.BinaryData; namespace Syroot.Worms.OnlineWorms.Server.Net { @@ -10,9 +11,11 @@ namespace Syroot.Worms.OnlineWorms.Server.Net { // ---- PROPERTIES --------------------------------------------------------------------------------------------- - public string Unknown { get; set; } + internal string Unknown { get; set; } - public string Version { get; set; } + internal string Unknown2 { get; set; } + + internal ushort Version { get; set; } // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- @@ -21,7 +24,9 @@ namespace Syroot.Worms.OnlineWorms.Server.Net internal override void Serialize(PacketStream stream) { stream.WriteString(Unknown); - stream.WriteString(Version); + stream.WriteByte(0); + stream.WriteString(Unknown2); + stream.WriteUInt16(Version); } } } diff --git a/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs b/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs index f3081bf..4ddee44 100644 --- a/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs +++ b/src/Syroot.OnlineWorms.Server/Net/Packets/RawQueryPacket.cs @@ -1,5 +1,4 @@ #if DEBUG - namespace Syroot.Worms.OnlineWorms.Server.Net { /// diff --git a/src/Syroot.OnlineWorms.Server/Server.cs b/src/Syroot.OnlineWorms.Server/Server.cs index 5e70c17..2cfaf81 100644 --- a/src/Syroot.OnlineWorms.Server/Server.cs +++ b/src/Syroot.OnlineWorms.Server/Server.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Net; using System.Net.Sockets; using System.Threading.Tasks; @@ -10,12 +9,20 @@ namespace Syroot.Worms.OnlineWorms.Server /// Represents a server listening for incoming client connections and dispatching them into /// instances. /// - public class Server + internal class Server { // ---- FIELDS ------------------------------------------------------------------------------------------------- private readonly List _clients = new List(); + // ---- PROPERTIES --------------------------------------------------------------------------------------------- + + internal string Name => "Online Worms Private Server"; + internal string RegionName => "Global"; + internal ushort Version => 114; + + internal Log Log { get; } = new Log(); + // ---- METHODS (INTERNAL) ------------------------------------------------------------------------------------- /// @@ -27,16 +34,20 @@ namespace Syroot.Worms.OnlineWorms.Server { TcpListener tcpListener = new TcpListener(IPAddress.Any, port); tcpListener.Start(); - Console.WriteLine($"Listening on port {port}..."); + Log.Write(LogCategory.Server, $"Listening on port {port}..."); - // Continually accept clients and dispatch them to their listening thread. while (true) { - Client client = new Client(tcpListener.AcceptTcpClient(), this); + // Continually accept clients. + TcpClient tcpClient = tcpListener.AcceptTcpClient(); + Log.Write(LogCategory.Connect, $"{tcpClient.Client.RemoteEndPoint} connected"); + Client client = new Client(tcpClient, this); _clients.Add(client); + // Dispatch the client into its listening thread and remove it when listening aborts. Task.Run(client.Listen).ContinueWith(_ => { + Log.Write(LogCategory.Disconnect, $"{client.TcpClient.Client.RemoteEndPoint} disconnected"); _clients.Remove(client); client.Dispose(); }); diff --git a/src/Syroot.OnlineWorms.Server/Syroot.Worms.OnlineWorms.Server.csproj b/src/Syroot.OnlineWorms.Server/Syroot.Worms.OnlineWorms.Server.csproj index 3129073..0d8db27 100644 --- a/src/Syroot.OnlineWorms.Server/Syroot.Worms.OnlineWorms.Server.csproj +++ b/src/Syroot.OnlineWorms.Server/Syroot.Worms.OnlineWorms.Server.csproj @@ -4,9 +4,6 @@ netcoreapp2.1 latest - - DEBUG;TRACE -