fix(network): Adds ConnectUO (#551)

- [X] Adds ability to create new protocol extension packets
- [X] Adds freeshard protocol extension support (packet 0xF1)
- [X] Adds ConnectUO support
- [X] Adds arbitrary read for `CircularBufferReader` and `SpanReader`
This commit is contained in:
Kamron Batman 2021-03-15 14:14:33 -07:00 committed by GitHub
parent 76970325a3
commit 5e3b42bf9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 381 additions and 162 deletions

View file

@ -10,7 +10,7 @@ namespace Server.Tests
public void TestGetBytes(string input, byte[] bytes)
{
Span<byte> outputBytes = stackalloc byte[input.Length / 2];
HexStringConverter.GetBytes(input, outputBytes);
input.GetBytes(outputBytes);
Assert.Equal(bytes, outputBytes.ToArray());
Assert.Equal(input, bytes.ToHexString());

View file

@ -374,5 +374,21 @@ namespace Server.Network
SeekOrigin.End => Length + offset,
_ => Position + offset // Current
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Read(Span<byte> 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));
}
}
}

View file

@ -278,5 +278,16 @@ namespace System.Buffers
_ => offset // Begin
});
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool Read(Span<byte> bytes)
{
if (bytes.Length < Length)
{
throw new ArgumentOutOfRangeException(nameof(bytes));
}
return _buffer.TryCopyTo(bytes);
}
}
}

View file

@ -89,7 +89,7 @@ namespace Server.Text
return result;
}
public static unsafe void GetBytes(string str, Span<byte> bytes)
public static unsafe void GetBytes(this string str, Span<byte> bytes)
{
fixed (char* strP = str)
{

View file

@ -46,7 +46,7 @@ namespace Server.Accounting.Security
public bool ValidatePassword(string encryptedPassword, string plainPassword)
{
Span<byte> 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);

View file

@ -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)));

View file

@ -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;
}

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
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<byte>(_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<byte>(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);
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
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);
}
}

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
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);
}
}
}

View file

@ -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 <http://www.gnu.org/licenses/>. *
*************************************************************************/
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;
}
}
}