From 4f28e1e3c4a53f23a04822adda657db414a7b7d2 Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sat, 25 Jun 2022 01:47:45 -0700 Subject: [PATCH] fix: Adds back razor/assistuo negotiations (#1089) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [X] Adds Razor negotiations * [X] Adds AssistUO "handshake" 🙄 --- .../Assistants/AssistantConfiguration.cs | 107 +++++++ Projects/Server/Assistants/AssistantEvents.cs | 43 +++ .../Server/Assistants/AssistantFeatures.cs | 55 ++++ .../Network/Packets/IncomingAccountPackets.cs | 57 +++- .../Network/Packets/OutgoingAccountPackets.cs | 172 ++++++----- Projects/UOContent/Accounting/Account.cs | 3 +- .../UOContent/Assistants/AssistantHandler.cs | 140 +++++++++ .../UOContent/Assistants/AssistantProtocol.cs | 19 ++ .../Items/Weapons/Artifacts/StaffOfTheMagi.cs | 2 +- Projects/UOContent/Network/MapUO.cs | 269 +++++++++--------- 10 files changed, 655 insertions(+), 212 deletions(-) create mode 100644 Projects/Server/Assistants/AssistantConfiguration.cs create mode 100644 Projects/Server/Assistants/AssistantEvents.cs create mode 100644 Projects/Server/Assistants/AssistantFeatures.cs create mode 100644 Projects/UOContent/Assistants/AssistantHandler.cs create mode 100644 Projects/UOContent/Assistants/AssistantProtocol.cs diff --git a/Projects/Server/Assistants/AssistantConfiguration.cs b/Projects/Server/Assistants/AssistantConfiguration.cs new file mode 100644 index 000000000..db275ee9d --- /dev/null +++ b/Projects/Server/Assistants/AssistantConfiguration.cs @@ -0,0 +1,107 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2022 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: AssistantConfiguration.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.IO; +using System.Runtime.CompilerServices; +using System.Text.Json.Serialization; +using Server.Json; + +namespace Server.Assistants; + +public static class AssistantConfiguration +{ + private const string _path = "Configuration/assistants.json"; + + public static bool Enabled { get; private set; } + + public static AssistantSettings Settings { get; private set; } + + private const string _defaultWarningMessage = "The server was unable to negotiate features with your assistant. " + + "You must download and run an updated version of UOSteam" + + " or Razor." + + "

Make sure you've checked the option Negotiate features with server, " + + "once you have this box checked you may log in and play normally." + + "

You will be disconnected shortly."; + + public static void Configure() + { + Enabled = ServerConfiguration.GetOrUpdateSetting("assistants.enableRazorNegotiation", false); + + var path = Path.Join(Core.BaseDirectory, _path); + + if (File.Exists(path)) + { + Settings = JsonConfig.Deserialize(path); + } + else + { + Settings = new AssistantSettings + { + WarnOnFailure = true, + KickOnFailure = true, + DisallowedFeatures = AssistantFeatures.None, + DisconnectDelay = TimeSpan.FromSeconds(15.0), + WarningMessage = _defaultWarningMessage + }; + + Save(path); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void DisallowFeature(AssistantFeatures feature) => SetDisallowed(feature, true); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void AllowFeature(AssistantFeatures feature) => SetDisallowed(feature, false); + + public static void SetDisallowed(AssistantFeatures feature, bool value) + { + if (value) + { + Settings.DisallowedFeatures |= feature; + } + else + { + Settings.DisallowedFeatures &= ~feature; + } + + Save(); + } + + private static void Save(string path = null) + { + path ??= Path.Join(Core.BaseDirectory, _path); + JsonConfig.Serialize(path, Settings); + } +} + +public record AssistantSettings +{ + [JsonPropertyName("warnOnFailure")] + public bool WarnOnFailure { get; set; } + + [JsonPropertyName("kickOnFailure")] + public bool KickOnFailure { get; set; } + + [JsonPropertyName("features")] + public AssistantFeatures DisallowedFeatures { get; set; } + + [JsonPropertyName("disconnectDelay")] + public TimeSpan DisconnectDelay { get; set; } + + [JsonPropertyName("warningMessage")] + public string WarningMessage { get; set; } +} diff --git a/Projects/Server/Assistants/AssistantEvents.cs b/Projects/Server/Assistants/AssistantEvents.cs new file mode 100644 index 000000000..9eee49e73 --- /dev/null +++ b/Projects/Server/Assistants/AssistantEvents.cs @@ -0,0 +1,43 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2022 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: AssistantEvents.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.Runtime.CompilerServices; +using Server.Accounting; +using Server.Network; + +namespace Server; + +public class AssistantAuthEventArgs +{ + public NetState State { get; } + public IAccount Account { get; } + public bool AuthOk { get; } + + public AssistantAuthEventArgs(NetState state, IAccount acct, bool authOK) + { + State = state; + Account = acct; + AuthOk = authOK; + } +} + +public static partial class EventSink +{ + public static event Action AssistantAuth; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void InvokeAssistantAuth(AssistantAuthEventArgs e) => AssistantAuth?.Invoke(e); +} diff --git a/Projects/Server/Assistants/AssistantFeatures.cs b/Projects/Server/Assistants/AssistantFeatures.cs new file mode 100644 index 000000000..5d29254ad --- /dev/null +++ b/Projects/Server/Assistants/AssistantFeatures.cs @@ -0,0 +1,55 @@ +/************************************************************************* + * ModernUO * + * Copyright 2019-2022 - ModernUO Development Team * + * Email: hi@modernuo.com * + * File: AssistantFeatures.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; + +namespace Server.Assistants; + +[Flags] +public enum AssistantFeatures : ulong +{ + None = 0, + + FilterWeather = 1ul << 0, // Weather Filter + FilterLight = 1ul << 1, // Light Filter + SmartTarget = 1ul << 2, // Smart Last Target + RangedTarget = 1ul << 3, // Range Check Last Target + AutoOpenDoors = 1ul << 4, // Automatically Open Doors + DequipOnCast = 1ul << 5, // Unequip Weapon on spell cast + AutoPotionEquip = 1ul << 6, // Un/Re-equip weapon on potion use + PoisonedChecks = 1ul << 7, // Block heal If poisoned/Macro If Poisoned condition/Heal or Cure self + LoopedMacros = 1ul << 8, // Disallow Looping macros, For loops, and macros that call other macros + UseOnceAgent = 1ul << 9, // The use once agent + RestockAgent = 1ul << 10, // The restock agent + SellAgent = 1ul << 11, // The sell agent + BuyAgent = 1ul << 12, // The buy agent + PotionHotkeys = 1ul << 13, // All potion hotkeys + RandomTargets = 1ul << 14, // All random target hotkeys (not target next, last target, target self) + ClosestTargets = 1ul << 15, // All closest target hotkeys + OverheadHealth = 1ul << 16, // Health and Mana/Stam messages shown over player's heads + + // AssistUO Only + AutolootAgent = 1ul << 17, // The autoloot agent + + BoneCutterAgent = 1ul << 18, // The bone cutter agent + JScriptMacros = 1ul << 19, // Javascript macro engine + AutoRemount = 1ul << 20, // Auto remount after dismount + AutoBandage = 1 << 21, // Auto bandage friends, self, last and mount option + EnemyTargetShare = 1 << 22, // Enemy target share on guild, party or alliance chat + FilterSeason = 1 << 23, // Season Filter + SpellTargetShare = 1 << 24, // Spell target share on guild, party or alliance chat + + All = ~None // Every feature possible +} diff --git a/Projects/Server/Network/Packets/IncomingAccountPackets.cs b/Projects/Server/Network/Packets/IncomingAccountPackets.cs index 33cb6117b..a5a078268 100644 --- a/Projects/Server/Network/Packets/IncomingAccountPackets.cs +++ b/Projects/Server/Network/Packets/IncomingAccountPackets.cs @@ -16,6 +16,7 @@ using System; using System.Collections.Generic; using System.IO; +using Server.Assistants; using CV = Server.ClientVersion; namespace Server.Network; @@ -69,9 +70,32 @@ public static class IncomingAccountPackets var flags = reader.ReadInt32(); reader.Seek(8, SeekOrigin.Current); int prof = reader.ReadByte(); - reader.Seek(15, SeekOrigin.Current); - int genderRace = reader.ReadByte(); + int genderRace; + if (AssistantConfiguration.Enabled && AssistantConfiguration.Settings.DisallowedFeatures != AssistantFeatures.None) + { + var razorFeatures = (AssistantFeatures)reader.ReadUInt64(); + var key = reader.ReadUInt64(); + + var authOk = razorFeatures == AssistantConfiguration.Settings.DisallowedFeatures && + // Highly recommend a shard changes these here, in razor and redistribute the assistant. + (key & 0xFFFFFFFFFFFFFF00ul) == 0x0911832B04178300ul; + + EventSink.InvokeAssistantAuth(new AssistantAuthEventArgs(state, state.Account, authOk)); + + if (!state.Running) + { + return; + } + + genderRace = (int)(key & 0xFF); + } + else + { + reader.Seek(15, SeekOrigin.Current); + genderRace = reader.ReadByte(); + } + var stats = new StatNameValue[] { @@ -124,7 +148,7 @@ public static class IncomingAccountPackets var info = state.CityInfo; var a = state.Account; - if (info == null || a == null || cityIndex < 0 || cityIndex >= info.Length) + if (info == null || a == null || cityIndex >= info.Length) { state.Disconnect("Invalid city selected during character creation."); return; @@ -230,7 +254,32 @@ public static class IncomingAccountPackets var flags = reader.ReadInt32(); - reader.Seek(24, SeekOrigin.Current); + if (AssistantConfiguration.Enabled && AssistantConfiguration.Settings.DisallowedFeatures != AssistantFeatures.None) + { + var razorFeatures = (AssistantFeatures)reader.ReadUInt64(); + var key1 = reader.ReadUInt64(); + var key2 = reader.ReadUInt64(); + + var authOk = razorFeatures == AssistantConfiguration.Settings.DisallowedFeatures && + // Highly recommend a shard changes these here, in razor and redistribute the assistant. + key1 == 0x0911832B04178305ul && key2 == 0x2485071787061988ul; + + EventSink.InvokeAssistantAuth(new AssistantAuthEventArgs(state, state.Account, authOk)); + } + else + { + reader.Seek(22, SeekOrigin.Current); + + if (reader.ReadUInt16() == 0xDEAD) + { + EventSink.InvokeAssistantAuth(new AssistantAuthEventArgs(state, state.Account, false)); + } + } + + if (!state.Running) + { + return; + } var charSlot = reader.ReadInt32(); reader.Seek(4, SeekOrigin.Current); // var clientIP = reader.ReadInt32(); diff --git a/Projects/Server/Network/Packets/OutgoingAccountPackets.cs b/Projects/Server/Network/Packets/OutgoingAccountPackets.cs index 59e6db66e..3df92939c 100644 --- a/Projects/Server/Network/Packets/OutgoingAccountPackets.cs +++ b/Projects/Server/Network/Packets/OutgoingAccountPackets.cs @@ -17,7 +17,9 @@ using System; using System.IO; using System.Buffers; using System.Runtime.CompilerServices; +using System.Security.Cryptography; using Server.Accounting; +using Server.Assistants; namespace Server.Network; @@ -54,12 +56,12 @@ public enum DeleteResultType public static class OutgoingAccountPackets { /** - * Packet: 0x81 - * Length: Up to 425 bytes - * - * Displays the list of characters during the login process. - * Note: Currently Unused - */ + * Packet: 0x81 + * Length: Up to 425 bytes + * + * Displays the list of characters during the login process. + * Note: Currently Unused + */ public static void SendChangeCharacter(this NetState ns, IAccount a) { if (ns == null || a == null) @@ -103,40 +105,40 @@ public static class OutgoingAccountPackets } /** - * Packet: 0xBD - * Length: 3 bytes - * - * Sends a requests for the client version - */ + * Packet: 0xBD + * Length: 3 bytes + * + * Sends a requests for the client version + */ [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SendClientVersionRequest(this NetState ns) => ns?.Send(stackalloc byte[] { 0xBD, 0x00, 0x03 }); /** - * Packet: 0x85 - * Length: 2 bytes - * - * Sends the result of a deletion request - */ + * Packet: 0x85 + * Length: 2 bytes + * + * Sends the result of a deletion request + */ [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SendCharacterDeleteResult(this NetState ns, DeleteResultType res) => ns?.Send(stackalloc byte[] { 0x85, (byte)res }); /** - * Packet: 0x53 - * Length: 2 bytes - * - * Sends a PopupMessage with a predetermined message - */ + * Packet: 0x53 + * Length: 2 bytes + * + * Sends a PopupMessage with a predetermined message + */ [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SendPopupMessage(this NetState ns, PMMessage msg) => ns?.Send(stackalloc byte[] { 0x53, (byte)msg }); /** - * Packet: 0xB9 - * Length: 3 or 5 bytes - * - * Sends support features based on the client version - */ + * Packet: 0xB9 + * Length: 3 or 5 bytes + * + * Sends support features based on the client version + */ public static void SendSupportedFeature(this NetState ns) { if (ns.CannotSendPackets()) @@ -182,11 +184,11 @@ public static class OutgoingAccountPackets } /** - * Packet: 0x1B - * Length: 37 bytes - * - * Sends login confirmation - */ + * Packet: 0x1B + * Length: 37 bytes + * + * Sends login confirmation + */ public static void SendLoginConfirmation(this NetState ns, Mobile m) { if (ns.CannotSendPackets()) @@ -223,22 +225,22 @@ public static class OutgoingAccountPackets } /** - * Packet: 0x55 - * Length: 1 byte - * - * Sends login completion - */ + * Packet: 0x55 + * Length: 1 byte + * + * Sends login completion + */ public static void SendLoginComplete(this NetState ns) { ns?.Send(stackalloc byte[] { 0x55 }); } /** - * Packet: 0x86 - * Length: Up to 424 bytes - * - * Sends updated character list - */ + * Packet: 0x86 + * Length: Up to 424 bytes + * + * Sends updated character list + */ public static void SendCharacterListUpdate(this NetState ns, IAccount a) { if (ns == null || a == null) @@ -284,12 +286,14 @@ public static class OutgoingAccountPackets ns.Send(writer.Span); } + private static MD5 _md5Provider; + /** - * Packet: 0xA9 - * Length: 1410 or more bytes - * - * Sends list of characters and starting cities. - */ + * Packet: 0xA9 + * Length: 1410 or more bytes + * + * Sends list of characters and starting cities. + */ public static void SendCharacterList(this NetState ns) { var acct = ns?.Account; @@ -315,14 +319,25 @@ public static class OutgoingAccountPackets } } - var count = Math.Max(Math.Max(highSlot + 1, acct.Limit), 5); + // Supported values are 1, 5, 6, or 7 + var count = Math.Max(highSlot + 1, acct.Limit); + if (count is not 1 and < 5) + { + count = 5; + } + var length = (client70130 ? 11 + (textLength * 2 + 25) * cityInfo.Length : 9 + (textLength * 2 + 1) * cityInfo.Length) + count * 60; var writer = new SpanWriter(stackalloc byte[length]); writer.Write((byte)0xA9); // Packet ID writer.Write((ushort)length); - writer.Write((byte)count); + writer.Write((byte)count); // TODO: It is probably more proper to use count. + + var enforceRazor = AssistantConfiguration.Enabled && + AssistantConfiguration.Settings.DisallowedFeatures != AssistantFeatures.None; + + Span hashBuffer = enforceRazor ? stackalloc byte[16] : null; for (int i = 0; i < count; i++) { @@ -330,12 +345,41 @@ public static class OutgoingAccountPackets if (m == null) { - writer.Clear(60); + writer.Clear(30); } else { var name = (m.RawName?.Trim()).DefaultIfNullOrEmpty("-no name-"); writer.WriteAscii(name, 30); + } + + if (enforceRazor) + { + writer.Write((byte)0); + var pos = writer.Position; + + if (i == 0) + { + writer.Write((ulong)AssistantConfiguration.Settings.DisallowedFeatures); + } + + if (count > 1 && i == 2 || count == 1 && i == 0) + { + _md5Provider ??= MD5.Create(); + if (_md5Provider.TryComputeHash(writer.Span, hashBuffer, out var bytesWritten) && bytesWritten == 16) + { + writer.Write(hashBuffer); + } + } + + var remaining = 28 - pos; + Utility.RandomBytes(writer.RawBuffer.Slice(writer.Position, remaining)); + writer.Seek(remaining, SeekOrigin.Current); + + writer.Write((byte)0); + } + else + { writer.Clear(30); // password } } @@ -387,21 +431,21 @@ public static class OutgoingAccountPackets } /** - * Packet: 0x82 - * Length: 2 bytes - * - * Sends a reason for rejecting the login - */ + * Packet: 0x82 + * Length: 2 bytes + * + * Sends a reason for rejecting the login + */ [MethodImpl(MethodImplOptions.AggressiveInlining)] public static void SendAccountLoginRejected(this NetState ns, ALRReason reason) => ns?.Send(stackalloc byte[] { 0x82, (byte)reason }); /** - * Packet: 0xA8 - * Length: 6 + 40 bytes per server listing - * - * Sends login acknowledge with server listing - */ + * Packet: 0xA8 + * Length: 6 + 40 bytes per server listing + * + * Sends login acknowledge with server listing + */ public static void SendAccountLoginAck(this NetState ns) { if (ns.CannotSendPackets()) @@ -433,11 +477,11 @@ public static class OutgoingAccountPackets } /** - * Packet: 0x8C - * Length: 11 bytes - * - * Sends acknowledge play server - */ + * Packet: 0x8C + * Length: 11 bytes + * + * Sends acknowledge play server + */ public static void SendPlayServerAck(this NetState ns, ServerInfo si, int authId) { if (ns.CannotSendPackets()) diff --git a/Projects/UOContent/Accounting/Account.cs b/Projects/UOContent/Accounting/Account.cs index 61b9dae64..25dd20570 100644 --- a/Projects/UOContent/Accounting/Account.cs +++ b/Projects/UOContent/Accounting/Account.cs @@ -448,8 +448,7 @@ namespace Server.Accounting /// Gets the maximum amount of characters allowed to be created on this account. Values other than 1, 5, 6, or 7 are not /// supported by the client. /// - public int Limit => Core.SA ? 7 : - Core.AOS ? 6 : 5; + public int Limit => Core.SA ? 7 : Core.AOS ? 6 : 5; /// /// Gets the maximum amount of characters that this account can hold. diff --git a/Projects/UOContent/Assistants/AssistantHandler.cs b/Projects/UOContent/Assistants/AssistantHandler.cs new file mode 100644 index 000000000..c924f1db5 --- /dev/null +++ b/Projects/UOContent/Assistants/AssistantHandler.cs @@ -0,0 +1,140 @@ +using System; +using System.Buffers; +using System.Collections.Generic; +using Server.Gumps; +using Server.Network; + +namespace Server.Assistants; + +public static class AssistantHandler +{ + public static bool Enabled { get; private set; } + + private static Dictionary _handshakes = new(); + + public static unsafe void Configure() + { + Enabled = ServerConfiguration.GetOrUpdateSetting("assistants.enableAssistUONegotiation", false); + + EventSink.AssistantAuth += OnAssistantAuth; + EventSink.Login -= OnLogin; + AssistantProtocol.Register(0xFF, false, &AssistUOHandshakeResponse); + } + + private static void OnAssistantAuth(AssistantAuthEventArgs e) + { + if (e.AuthOk) + { + return; + } + + var m = e.State.Mobile; + var isPlayer = m.AccessLevel <= AccessLevel.Player; + var willKick = AssistantConfiguration.Settings.KickOnFailure; + var delay = AssistantConfiguration.Settings.DisconnectDelay; + + if (willKick && isPlayer && delay <= TimeSpan.Zero) + { + e.State.Disconnect("Failed to negotiate assistant features."); + return; + } + + if (AssistantConfiguration.Settings.WarnOnFailure) + { + var warningGump = new WarningGump( + 1060635, + 30720, + AssistantConfiguration.Settings.WarningMessage, + 0xFFC000, + 420, + 250, + null, + false + ); + + m.SendGump(warningGump); + } + + if (isPlayer) + { + _handshakes[m] = Timer.DelayCall(delay, OnForceDisconnect, m); + } + + e.State.LogInfo("Failed to negotiate assistant features."); + } + + private static void OnLogin(Mobile m) + { + if (m?.NetState?.Running != true || !AssistantConfiguration.Enabled) + { + return; + } + + m.NetState.SendAssistUOHandshake(); + + if (_handshakes.TryGetValue(m, out var t)) + { + t?.Stop(); + } + + _handshakes[m] = Timer.DelayCall(TimeSpan.FromSeconds(30), OnTimeout, m); + } + + private static void AssistUOHandshakeResponse(NetState state, CircularBufferReader reader, int packetLength) + { + Mobile m = state.Mobile; + + if (m == null || !_handshakes.TryGetValue(m, out var t)) + { + return; + } + + t?.Stop(); + _handshakes.Remove(m); + + EventSink.InvokeAssistantAuth(new AssistantAuthEventArgs(m.NetState, m.Account, true)); + } + + private static void OnTimeout(Mobile m) + { + if (m == null || !_handshakes.TryGetValue(m, out var t)) + { + return; + } + + t?.Stop(); + _handshakes.Remove(m); + + if (m.NetState?.Running != true || !Enabled) + { + return; + } + + EventSink.InvokeAssistantAuth(new AssistantAuthEventArgs(m.NetState, m.Account, false)); + } + + private static void OnForceDisconnect(Mobile m) + { + if (m == null) + { + return; + } + + m.NetState?.Disconnect($"Player {m} kicked (Failed assistant handshake)"); + _handshakes.Remove(m); + } + + public static void SendAssistUOHandshake(this NetState ns) + { + if (ns?.CannotSendPackets() != false) + { + return; + } + + var writer = new SpanWriter(stackalloc byte[12]); + writer.Write((byte)0xF0); // Packet ID + writer.Write((ushort)12); + writer.Write((byte)0xFE); // Command + writer.Write((ulong)AssistantConfiguration.Settings.DisallowedFeatures); + } +} diff --git a/Projects/UOContent/Assistants/AssistantProtocol.cs b/Projects/UOContent/Assistants/AssistantProtocol.cs new file mode 100644 index 000000000..90bad5eeb --- /dev/null +++ b/Projects/UOContent/Assistants/AssistantProtocol.cs @@ -0,0 +1,19 @@ +namespace Server.Network; + +public static class AssistantProtocol +{ + private static PacketHandler[] _handlers; + + public static void Configure() + { + _handlers = ProtocolExtensions.Register(new AssistantsProtocolInfo()); + } + + public static unsafe void Register(int cmd, bool ingame, delegate* onReceive) => + _handlers[cmd] = new PacketHandler(cmd, 0, ingame, onReceive); + + private struct AssistantsProtocolInfo : IProtocolExtensionsInfo + { + public int PacketId => 0xF0; + } +} diff --git a/Projects/UOContent/Items/Weapons/Artifacts/StaffOfTheMagi.cs b/Projects/UOContent/Items/Weapons/Artifacts/StaffOfTheMagi.cs index 35aa73085..efa426329 100644 --- a/Projects/UOContent/Items/Weapons/Artifacts/StaffOfTheMagi.cs +++ b/Projects/UOContent/Items/Weapons/Artifacts/StaffOfTheMagi.cs @@ -3,7 +3,7 @@ using ModernUO.Serialization; namespace Server.Items { [SerializationGenerator(0, false)] - public partial class StaffOfTheMagi : BlackStaff + public partial class StaffOfTheMagi : BlackStaff { [Constructible] public StaffOfTheMagi() diff --git a/Projects/UOContent/Network/MapUO.cs b/Projects/UOContent/Network/MapUO.cs index 11f7305ea..751d0402f 100644 --- a/Projects/UOContent/Network/MapUO.cs +++ b/Projects/UOContent/Network/MapUO.cs @@ -19,151 +19,138 @@ using System.IO; using Server.Engines.PartySystem; using Server.Guilds; -namespace Server.Network +namespace Server.Network; + +public static class MapUO { - public static class MapUO + public static unsafe void Configure() { - private static PacketHandler[] _handlers; + AssistantProtocol.Register(0x00, true, &QueryPartyMemberLocations); + AssistantProtocol.Register(0x01, true, &QueryGuildMemberLocations); + } - public static unsafe void Configure() + public static void QueryGuildMemberLocations(NetState state, CircularBufferReader reader, int packetLength) + { + Mobile from = state.Mobile; + + state.SendGuildMemberLocations(from, from.Guild as Guild, reader.ReadBoolean()); + } + + public static void QueryPartyMemberLocations(NetState state, CircularBufferReader reader, int packetLength) + { + Mobile from = state.Mobile; + var party = Party.Get(from); + + if (party != null) { - _handlers = ProtocolExtensions.Register(new MapUOProtocolInfo()); - - Register(0x00, true, &QueryPartyMemberLocations); - Register(0x01, true, &QueryGuildMemberLocations); - } - - public static unsafe void Register(int cmd, bool ingame, delegate* onReceive) => - _handlers[cmd] = new PacketHandler(cmd, 0, ingame, onReceive); - - public static void QueryGuildMemberLocations(NetState state, CircularBufferReader reader, int packetLength) - { - Mobile from = state.Mobile; - - state.SendGuildMemberLocations(from, from.Guild as Guild, reader.ReadBoolean()); - } - - public static void QueryPartyMemberLocations(NetState state, CircularBufferReader reader, 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.CannotSendPackets()) - { - 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.Location, m.Location) && 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.CannotSendPackets()) - { - 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.Location, mob.Location) && 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); - } - - private struct MapUOProtocolInfo : IProtocolExtensionsInfo - { - public int PacketId => 0xF0; + state.SendPartyMemberLocations(from, party); } } + + public static void SendGuildMemberLocations(this NetState ns, Mobile from, Guild guild, bool sendLocations) + { + if (ns.CannotSendPackets()) + { + 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.Location, m.Location) && 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.CannotSendPackets()) + { + 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.Location, mob.Location) && 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); + } }