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);
+ }
}