diff --git a/Projects/Server.Tests/Tests/Utility/HexStringConverterTest.cs b/Projects/Server.Tests/Tests/Utility/HexStringConverterTest.cs index b435a1d64..e1a31273c 100644 --- a/Projects/Server.Tests/Tests/Utility/HexStringConverterTest.cs +++ b/Projects/Server.Tests/Tests/Utility/HexStringConverterTest.cs @@ -10,7 +10,7 @@ namespace Server.Tests public void TestGetBytes(string input, byte[] bytes) { Span outputBytes = stackalloc byte[input.Length / 2]; - HexStringConverter.GetBytes(input, outputBytes); + input.GetBytes(outputBytes); Assert.Equal(bytes, outputBytes.ToArray()); Assert.Equal(input, bytes.ToHexString()); diff --git a/Projects/Server/Buffers/CircularBufferReader.cs b/Projects/Server/Buffers/CircularBufferReader.cs index af822cfde..5683ce165 100644 --- a/Projects/Server/Buffers/CircularBufferReader.cs +++ b/Projects/Server/Buffers/CircularBufferReader.cs @@ -374,5 +374,21 @@ namespace Server.Network SeekOrigin.End => Length + offset, _ => Position + offset // Current }; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Read(Span bytes) + { + if (bytes.Length < Length) + { + throw new ArgumentOutOfRangeException(nameof(bytes)); + } + + if (_first.Length > 0 && !_first.TryCopyTo(bytes)) + { + return false; + } + + return _second.Length <= 0 || _second.TryCopyTo(bytes.Slice(_first.Length)); + } } } diff --git a/Projects/Server/Buffers/SpanReader.cs b/Projects/Server/Buffers/SpanReader.cs index 90eec159a..71faaf5ce 100644 --- a/Projects/Server/Buffers/SpanReader.cs +++ b/Projects/Server/Buffers/SpanReader.cs @@ -278,5 +278,16 @@ namespace System.Buffers _ => offset // Begin }); } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool Read(Span bytes) + { + if (bytes.Length < Length) + { + throw new ArgumentOutOfRangeException(nameof(bytes)); + } + + return _buffer.TryCopyTo(bytes); + } } } diff --git a/Projects/Server/Text/HexStringConverter.cs b/Projects/Server/Text/HexStringConverter.cs index 5069d31c8..28b786de3 100644 --- a/Projects/Server/Text/HexStringConverter.cs +++ b/Projects/Server/Text/HexStringConverter.cs @@ -89,7 +89,7 @@ namespace Server.Text return result; } - public static unsafe void GetBytes(string str, Span bytes) + public static unsafe void GetBytes(this string str, Span bytes) { fixed (char* strP = str) { diff --git a/Projects/UOContent/Accounting/Security/PBKDF2PasswordProtection.cs b/Projects/UOContent/Accounting/Security/PBKDF2PasswordProtection.cs index a54e1f25a..9086b07c4 100644 --- a/Projects/UOContent/Accounting/Security/PBKDF2PasswordProtection.cs +++ b/Projects/UOContent/Accounting/Security/PBKDF2PasswordProtection.cs @@ -46,7 +46,7 @@ namespace Server.Accounting.Security public bool ValidatePassword(string encryptedPassword, string plainPassword) { Span encryptedBytes = stackalloc byte[m_OutputSize]; - HexStringConverter.GetBytes(encryptedPassword, encryptedBytes); + encryptedPassword.GetBytes(encryptedBytes); var iterations = BinaryPrimitives.ReadUInt16LittleEndian(encryptedBytes.SliceToLength(2)); var salt = encryptedBytes.Slice(2, m_SaltSize); diff --git a/Projects/UOContent/Gumps/AdminGump.cs b/Projects/UOContent/Gumps/AdminGump.cs index 7e899b6f1..4f1b2eb2f 100644 --- a/Projects/UOContent/Gumps/AdminGump.cs +++ b/Projects/UOContent/Gumps/AdminGump.cs @@ -185,7 +185,7 @@ namespace Server.Gumps AddLabel(150, 270, LabelHue, Core.ScriptItems.ToString()); AddLabel(20, 290, LabelHue, "Uptime:"); - AddLabel(150, 290, LabelHue, FormatTimeSpan(Core.Now - Clock.ServerStart)); + AddLabel(150, 290, LabelHue, FormatTimeSpan(TimeSpan.FromMilliseconds(Core.TickCount))); AddLabel(20, 310, LabelHue, "Memory:"); AddLabel(150, 310, LabelHue, FormatByteAmount(GC.GetTotalMemory(false))); diff --git a/Projects/UOContent/Items/Skill Items/Tinkering/Clocks.cs b/Projects/UOContent/Items/Skill Items/Tinkering/Clocks.cs index 4dde6b0f5..346853307 100644 --- a/Projects/UOContent/Items/Skill Items/Tinkering/Clocks.cs +++ b/Projects/UOContent/Items/Skill Items/Tinkering/Clocks.cs @@ -31,7 +31,7 @@ namespace Server.Items public static DateTime ServerStart { get; private set; } - public static void Initialize() + public static void Configure() { ServerStart = Core.Now; } diff --git a/Projects/UOContent/Network/ConnectUO.cs b/Projects/UOContent/Network/ConnectUO.cs new file mode 100644 index 000000000..96c3766a0 --- /dev/null +++ b/Projects/UOContent/Network/ConnectUO.cs @@ -0,0 +1,122 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ConnectUO.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Buffers; +using Server.Accounting; +using Server.Text; + +namespace Server.Network +{ + public static class ConnectUO + { + public enum ConnectUOServerType + { + RunUO, + ServUO, + UOX3, + POL, + Sphere, + ModernUO + } + + public const byte ConnectUOProtocolVersion = 0; + private const int _connectUOTokenLength = 32; + private const ConnectUOServerType _serverType = ConnectUOServerType.ModernUO; + private static byte[] _token; + + public static void Configure() + { + var enabled = ServerConfiguration.GetOrUpdateSetting("connectuo.enabled", true); + var token = ServerConfiguration.GetOrUpdateSetting("connectuo.token", null); + + if (enabled) + { + try + { + if (!string.IsNullOrWhiteSpace(token)) + { + if (token.Length != _connectUOTokenLength * 2) + { + throw new Exception("Invalid length for ConnectUO token"); + } + + _token = GC.AllocateUninitializedArray(_connectUOTokenLength); + token.GetBytes(_token); + } + } + catch + { + Utility.PushColor(ConsoleColor.Red); + Console.WriteLine("ConnectUO token could not be parsed"); + Console.WriteLine("Make sure modernuo.json is properly configured"); + Utility.PopColor(); + _token = null; + } + + FreeshardProtocol.Register(0xC0, false, PollInfo); + } + } + + public static void PollInfo(NetState ns, CircularBufferReader reader, ref int packetLength) + { + var version = reader.ReadByte(); + + if (_token != null) + { + unsafe { + byte* tok = stackalloc byte[_token.Length]; + var span = new Span(tok, _token.Length); + reader.Read(span); + + if (!span.SequenceEqual(_token)) + { + ns.Disconnect("Invalid token sent for ConnectUO"); + return; + } + } + } + + ns.WriteConsole($"ConnectUO (v{version}) is requesting stats."); + if (version > ConnectUOProtocolVersion) + { + Utility.PushColor(ConsoleColor.Yellow); + ns.WriteConsole("Warning! ConnectUO (v{version}) is newer than what is supported."); + Utility.PopColor(); + } + + ns.SendServerPollInfo(); + } + + public static void SendServerPollInfo(this NetState ns) + { + if (ns == null) + { + return; + } + + var writer = new SpanWriter(stackalloc byte[15]); + writer.Write((byte)0xC0); // Packet ID + writer.Write(17); // Length + writer.Write(ConnectUOProtocolVersion); // Version + writer.Write((byte)_serverType); + writer.Write((int)(Core.TickCount / 1000)); + writer.Write(Accounts.Count); // Shame if you modify this! + writer.Write(TcpServer.Instances.Count); // Shame if you modify this! + + ns.Send(writer.Span); + } + } +} diff --git a/Projects/UOContent/Network/FreeshardProtocol.cs b/Projects/UOContent/Network/FreeshardProtocol.cs new file mode 100644 index 000000000..93b6a49b3 --- /dev/null +++ b/Projects/UOContent/Network/FreeshardProtocol.cs @@ -0,0 +1,31 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: FreeshardProtocol.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +namespace Server.Network +{ + public static class FreeshardProtocol + { + private static PacketHandler[] _handlers; + + [CallPriority(10)] + public static void Configure() + { + _handlers = ProtocolExtensions.Register(0xF1); + } + + public static void Register(int cmd, bool ingame, OnPacketReceive onReceive) => + _handlers[cmd] = new PacketHandler(cmd, 0, ingame, onReceive); + } +} diff --git a/Projects/UOContent/Network/MapUO.cs b/Projects/UOContent/Network/MapUO.cs new file mode 100644 index 000000000..ac82b4ab3 --- /dev/null +++ b/Projects/UOContent/Network/MapUO.cs @@ -0,0 +1,164 @@ +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: MapUO.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ + +using System; +using System.Buffers; +using System.IO; +using Server.Engines.PartySystem; +using Server.Guilds; + +namespace Server.Network +{ + public static class MapUO + { + private static PacketHandler[] _handlers; + + public static void Configure() + { + _handlers = ProtocolExtensions.Register(0xF0); + + Register(0x00, true, QueryPartyMemberLocations); + Register(0x01, true, QueryGuildMemberLocations); + } + + public static void Register(int cmd, bool ingame, OnPacketReceive onReceive) => + _handlers[cmd] = new PacketHandler(cmd, 0, ingame, onReceive); + + public static void QueryGuildMemberLocations(NetState state, CircularBufferReader reader, ref int packetLength) + { + Mobile from = state.Mobile; + + state.SendGuildMemberLocations(from, from.Guild as Guild, reader.ReadBoolean()); + } + + public static void QueryPartyMemberLocations(NetState state, CircularBufferReader reader, ref int packetLength) + { + Mobile from = state.Mobile; + var party = Party.Get(from); + + if (party != null) + { + state.SendPartyMemberLocations(from, party); + } + } + + public static void SendGuildMemberLocations(this NetState ns, Mobile from, Guild guild, bool sendLocations) + { + if (ns == null) + { + return; + } + + var count = guild?.Members.Count ?? 0; + var maxLength = 9 + (count > 1 ? (count - 1) * (sendLocations ? 10 : 4) : 0); + var writer = new SpanWriter(stackalloc byte[maxLength]); + writer.Write((byte)0xF0); // Packet ID + writer.Seek(2, SeekOrigin.Current); + writer.Write((byte)0x02); // Command + writer.Write(count > 0 && sendLocations); + + bool sendPacket = false; + for (var i = 0; i < count; i++) + { + var m = guild!.Members[i]; + + if (m?.NetState == null || m == from) + { + continue; + } + + if (sendLocations && Utility.InUpdateRange(from, m) && from.CanSee(m)) + { + continue; + } + + sendPacket = true; + writer.Write(m.Serial); + + if (sendLocations) + { + writer.Write((short)m.X); + writer.Write((short)m.Y); + writer.Write((byte)(m.Map?.MapID ?? 0)); + + if (m.Alive) + { + writer.Write((byte)(m.Hits * 100 / Math.Max(m.HitsMax, 1))); + } + else + { + writer.Write((byte)0); + } + } + } + + if (!sendPacket) + { + return; + } + + writer.Write(0); + writer.WritePacketLength(); + ns.Send(writer.Span); + } + + public static void SendPartyMemberLocations(this NetState ns, Mobile from, Party party) + { + if (ns == null) + { + return; + } + + var count = party?.Members.Count ?? 0; + var maxLength = 9 + (count > 1 ? (count - 1) * 9 : 0); + var writer = new SpanWriter(stackalloc byte[maxLength]); + writer.Write((byte)0xF0); // Packet ID + writer.Seek(2, SeekOrigin.Current); + writer.Write((byte)0x01); // Command + + bool sendPacket = false; + for (var i = 0; i < count; i++) + { + var pmi = party!.Members[i]; + Mobile mob = pmi?.Mobile; + + if (mob?.NetState == null || mob == from) + { + continue; + } + + if (Utility.InUpdateRange(from, mob) && from.CanSee(mob)) + { + continue; + } + + sendPacket = true; + writer.Write(mob.Serial); + writer.Write((short)mob.X); + writer.Write((short)mob.Y); + writer.Write((byte)(mob.Map?.MapID ?? 0)); + } + + if (!sendPacket) + { + return; + } + + writer.Write(0); + writer.WritePacketLength(); + ns.Send(writer.Span); + } + } +} diff --git a/Projects/UOContent/Network/ProtocolExtensions.cs b/Projects/UOContent/Network/ProtocolExtensions.cs index 09b5a5756..abdc6681b 100644 --- a/Projects/UOContent/Network/ProtocolExtensions.cs +++ b/Projects/UOContent/Network/ProtocolExtensions.cs @@ -1,179 +1,54 @@ -using System; -using System.Buffers; -using System.IO; -using Server.Engines.PartySystem; -using Server.Guilds; +/************************************************************************* + * ModernUO * + * Copyright (C) 2019-2021 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: ProtocolExtension.cs * + * * + * This program is free software: you can redistribute it and/or modify * + * it 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. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program. If not, see . * + *************************************************************************/ namespace Server.Network { public static class ProtocolExtensions { - private static readonly PacketHandler[] _handlers = new PacketHandler[0x100]; - - public static void Configure() + public static PacketHandler[] Register(byte packetId) { - IncomingPackets.Register( 0xF0, 0, false, DecodeBundledPacket); - Register(0x00, true, QueryPartyMemberLocations); - Register(0x01, true, QueryGuildMemberLocations); - } + var packetHandlers = new PacketHandler[0x100]; - public static void Register(int packetID, bool ingame, OnPacketReceive onReceive) - { - _handlers[packetID] = new PacketHandler(packetID, 0, ingame, onReceive); - } - - public static PacketHandler GetHandler(int packetID) => - packetID >= 0 && packetID < _handlers.Length ? _handlers[packetID] : null; - - public static void DecodeBundledPacket(NetState state, CircularBufferReader reader, ref int packetLength) - { - int packetID = reader.ReadByte(); - - PacketHandler ph = GetHandler(packetID); - - if (ph == null) + void DecodeBundledPacket(NetState state, CircularBufferReader reader, ref int packetLength) { - return; - } + int cmd = reader.ReadByte(); - if (ph.Ingame && state.Mobile == null) - { - state.WriteConsole("Sent in-game packet (0xBFx{0:X2}) before having been attached to a mobile", packetID); - state.Disconnect("Sent in-game packet before being attached to a mobile."); - } - else if (ph.Ingame && state.Mobile.Deleted) - { - state.Disconnect(string.Empty); - } - else - { - ph.OnReceive(state, reader, ref packetLength); - } - } + PacketHandler ph = cmd >= 0 && cmd < packetHandlers.Length ? packetHandlers[cmd] : null; - public static void QueryGuildMemberLocations(NetState state, CircularBufferReader reader, ref int packetLength) - { - Mobile from = state.Mobile; - - state.SendGuildMemberLocations(from, from.Guild as Guild, reader.ReadBoolean()); - } - - public static void QueryPartyMemberLocations(NetState state, CircularBufferReader reader, ref int packetLength) - { - Mobile from = state.Mobile; - var party = Party.Get(from); - - if (party != null) - { - state.SendPartyMemberLocations(from, party); - } - } - - public static void SendGuildMemberLocations(this NetState ns, Mobile from, Guild guild, bool sendLocations) - { - if (ns == null) - { - return; - } - - var count = guild?.Members.Count ?? 0; - var maxLength = 9 + (count > 1 ? (count - 1) * (sendLocations ? 10 : 4) : 0); - var writer = new SpanWriter(stackalloc byte[maxLength]); - writer.Write((byte)0xF0); // Packet ID - writer.Seek(2, SeekOrigin.Current); - writer.Write((byte)0x02); // Command - writer.Write(count > 0 && sendLocations); - - bool sendPacket = false; - for (var i = 0; i < count; i++) - { - var m = guild!.Members[i]; - - if (m?.NetState == null || m == from) + if (ph == null) { - continue; + return; } - if (sendLocations && Utility.InUpdateRange(from, m) && from.CanSee(m)) + if (ph.Ingame && state.Mobile == null) { - continue; + state.WriteConsole("Sent in-game packet (0x{0:X2}x{1:X2}) before having been attached to a mobile", packetId, cmd); + state.Disconnect("Sent in-game packet before being attached to a mobile."); } - - sendPacket = true; - writer.Write(m.Serial); - - if (sendLocations) + else if (ph.Ingame && state.Mobile.Deleted) { - writer.Write((short)m.X); - writer.Write((short)m.Y); - writer.Write((byte)(m.Map?.MapID ?? 0)); - - if (m.Alive) - { - writer.Write((byte)(m.Hits * 100 / Math.Max(m.HitsMax, 1))); - } - else - { - writer.Write((byte)0); - } + state.Disconnect(string.Empty); + } + else + { + ph.OnReceive(state, reader, ref packetLength); } } - if (!sendPacket) - { - return; - } - - writer.Write(0); - writer.WritePacketLength(); - ns.Send(writer.Span); - } - - public static void SendPartyMemberLocations(this NetState ns, Mobile from, Party party) - { - if (ns == null) - { - return; - } - - var count = party?.Members.Count ?? 0; - var maxLength = 9 + (count > 1 ? (count - 1) * 9 : 0); - var writer = new SpanWriter(stackalloc byte[maxLength]); - writer.Write((byte)0xF0); // Packet ID - writer.Seek(2, SeekOrigin.Current); - writer.Write((byte)0x01); // Command - - bool sendPacket = false; - for (var i = 0; i < count; i++) - { - var pmi = party!.Members[i]; - Mobile mob = pmi?.Mobile; - - if (mob?.NetState == null || mob == from) - { - continue; - } - - if (Utility.InUpdateRange(from, mob) && from.CanSee(mob)) - { - continue; - } - - sendPacket = true; - writer.Write(mob.Serial); - writer.Write((short)mob.X); - writer.Write((short)mob.Y); - writer.Write((byte)(mob.Map?.MapID ?? 0)); - } - - if (!sendPacket) - { - return; - } - - writer.Write(0); - writer.WritePacketLength(); - ns.Send(writer.Span); + IncomingPackets.Register(packetId, 0, false, DecodeBundledPacket); + return packetHandlers; } } }