fix: Fixes stalled connections and infinite throttle (#1796)
> [!Warning] > **Developer Warning** > The `PacketThrottle` callback return value is now reversed. `true` indicates the connection is _throttled_. ### Summary - Fixes an issue where connections get stalled forever - Fixes an issue where the throttler is not working properly - Removes account attack limiter - Rewrites IP limiter - Removes IP restrictions (they weren't used, and not practical) - Fixes issue where IP limiter was counting before firewall was blocking. View without whitespace: https://github.com/modernuo/ModernUO/pull/1796/files?diff=split&w=1
This commit is contained in:
parent
ccad915464
commit
a4522b9d43
13 changed files with 1538 additions and 1783 deletions
|
|
@ -3,121 +3,136 @@ using System.IO;
|
|||
using System.Net;
|
||||
using Server.Accounting.Security;
|
||||
|
||||
namespace Server.Accounting
|
||||
namespace Server.Accounting;
|
||||
|
||||
public partial class Account
|
||||
{
|
||||
public partial class Account
|
||||
// Deleted IP Restrictions
|
||||
private void MigrateFrom(V5Content content)
|
||||
{
|
||||
// Username was not interned
|
||||
private void MigrateFrom(V4Content content)
|
||||
_username = content.Username;
|
||||
_passwordAlgorithm = content.PasswordAlgorithm;
|
||||
_password = content.Password;
|
||||
_accessLevel = content.AccessLevel;
|
||||
_flags = content.Flags;
|
||||
_lastLogin = content.LastLogin;
|
||||
_totalGold = content.TotalGold;
|
||||
_totalPlat = content.TotalPlat;
|
||||
_mobiles = content.Mobiles;
|
||||
_comments = content.Comments;
|
||||
_tags = content.Tags;
|
||||
_loginIPs = content.LoginIPs;
|
||||
_totalGameTime = content.TotalGameTime;
|
||||
_email = content.Email;
|
||||
}
|
||||
|
||||
// Username was not interned
|
||||
private void MigrateFrom(V4Content content)
|
||||
{
|
||||
_username = content.Username;
|
||||
_passwordAlgorithm = content.PasswordAlgorithm;
|
||||
_password = content.Password;
|
||||
_accessLevel = content.AccessLevel;
|
||||
_flags = content.Flags;
|
||||
_lastLogin = content.LastLogin;
|
||||
_totalGold = content.TotalGold;
|
||||
_totalPlat = content.TotalPlat;
|
||||
_mobiles = content.Mobiles;
|
||||
_comments = content.Comments;
|
||||
_tags = content.Tags;
|
||||
_loginIPs = content.LoginIPs;
|
||||
_totalGameTime = content.TotalGameTime;
|
||||
_email = content.Email;
|
||||
}
|
||||
|
||||
private void MigrateFrom(V3Content content)
|
||||
{
|
||||
_username = content.Username;
|
||||
_username.Intern();
|
||||
_passwordAlgorithm = content.PasswordAlgorithm;
|
||||
_password = content.Password;
|
||||
_accessLevel = content.AccessLevel;
|
||||
_flags = content.Flags;
|
||||
Created = content.Created;
|
||||
_lastLogin = content.LastLogin;
|
||||
_totalGold = content.TotalGold;
|
||||
_totalPlat = content.TotalPlat;
|
||||
_mobiles = content.Mobiles;
|
||||
_comments = content.Comments;
|
||||
_tags = content.Tags;
|
||||
_loginIPs = content.LoginIPs;
|
||||
_totalGameTime = content.TotalGameTime;
|
||||
_email = content.Email;
|
||||
}
|
||||
|
||||
private void Deserialize(IGenericReader reader, int version)
|
||||
{
|
||||
if (version != 2)
|
||||
{
|
||||
_username = content.Username;
|
||||
_passwordAlgorithm = content.PasswordAlgorithm;
|
||||
_password = content.Password;
|
||||
_accessLevel = content.AccessLevel;
|
||||
_flags = content.Flags;
|
||||
_lastLogin = content.LastLogin;
|
||||
_totalGold = content.TotalGold;
|
||||
_totalPlat = content.TotalPlat;
|
||||
_mobiles = content.Mobiles;
|
||||
_comments = content.Comments;
|
||||
_tags = content.Tags;
|
||||
_loginIPs = content.LoginIPs;
|
||||
_ipRestrictions = content.IpRestrictions;
|
||||
_totalGameTime = content.TotalGameTime;
|
||||
_email = content.Email;
|
||||
// Due to a bug where we were not versioning at all, reset so we don't have an issue deserializing
|
||||
reader.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
private void MigrateFrom(V3Content content)
|
||||
_username = reader.ReadString(true);
|
||||
_passwordAlgorithm = version < 2 ? (PasswordProtectionAlgorithm)reader.ReadInt() : reader.ReadEnum<PasswordProtectionAlgorithm>();
|
||||
_password = reader.ReadString();
|
||||
_accessLevel = version < 2 ? (AccessLevel)reader.ReadInt() : reader.ReadEnum<AccessLevel>();
|
||||
_flags = reader.ReadInt();
|
||||
Created = reader.ReadDateTime();
|
||||
_lastLogin = reader.ReadDateTime();
|
||||
|
||||
_totalGold = reader.ReadInt();
|
||||
_totalPlat = reader.ReadInt();
|
||||
|
||||
var length = reader.ReadInt();
|
||||
_mobiles = new Mobile[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_username = content.Username;
|
||||
_username.Intern();
|
||||
_passwordAlgorithm = content.PasswordAlgorithm;
|
||||
_password = content.Password;
|
||||
_accessLevel = content.AccessLevel;
|
||||
_flags = content.Flags;
|
||||
Created = content.Created;
|
||||
_lastLogin = content.LastLogin;
|
||||
_totalGold = content.TotalGold;
|
||||
_totalPlat = content.TotalPlat;
|
||||
_mobiles = content.Mobiles;
|
||||
_comments = content.Comments;
|
||||
_tags = content.Tags;
|
||||
_loginIPs = content.LoginIPs;
|
||||
_ipRestrictions = content.IpRestrictions;
|
||||
_totalGameTime = content.TotalGameTime;
|
||||
_email = content.Email;
|
||||
_mobiles[i] = reader.ReadEntity<Mobile>();
|
||||
}
|
||||
|
||||
private void Deserialize(IGenericReader reader, int version)
|
||||
length = reader.ReadInt();
|
||||
_comments = length > 0 ? new List<AccountComment>(length) : null;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (version != 2)
|
||||
_comments!.Add(new AccountComment(reader));
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_tags = length > 0 ? new List<AccountTag>(length) : null;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_tags!.Add(new AccountTag(reader));
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_loginIPs = new IPAddress[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (version < 2)
|
||||
{
|
||||
// Due to a bug where we were not versioning at all, reset so we don't have an issue deserializing
|
||||
reader.Seek(0, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
_username = reader.ReadString(true);
|
||||
_passwordAlgorithm = version < 2 ? (PasswordProtectionAlgorithm)reader.ReadInt() : reader.ReadEnum<PasswordProtectionAlgorithm>();
|
||||
_password = reader.ReadString();
|
||||
_accessLevel = version < 2 ? (AccessLevel)reader.ReadInt() : reader.ReadEnum<AccessLevel>();
|
||||
_flags = reader.ReadInt();
|
||||
Created = reader.ReadDateTime();
|
||||
_lastLogin = reader.ReadDateTime();
|
||||
|
||||
_totalGold = reader.ReadInt();
|
||||
_totalPlat = reader.ReadInt();
|
||||
|
||||
var length = reader.ReadInt();
|
||||
_mobiles = new Mobile[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_mobiles[i] = reader.ReadEntity<Mobile>();
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_comments = length > 0 ? new List<AccountComment>(length) : null;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_comments!.Add(new AccountComment(reader));
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_tags = length > 0 ? new List<AccountTag>(length) : null;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
_tags!.Add(new AccountTag(reader));
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_loginIPs = new IPAddress[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
if (version < 2)
|
||||
if (IPAddress.TryParse(reader.ReadString(), out var address))
|
||||
{
|
||||
if (IPAddress.TryParse(reader.ReadString(), out var address))
|
||||
{
|
||||
_loginIPs[i] = Utility.Intern(address);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_loginIPs[i] = reader.ReadIPAddress();
|
||||
_loginIPs[i] = Utility.Intern(address);
|
||||
}
|
||||
}
|
||||
|
||||
length = reader.ReadInt();
|
||||
_ipRestrictions = new string[length];
|
||||
for (int i = 0; i < length; i++)
|
||||
else
|
||||
{
|
||||
_ipRestrictions[i] = reader.ReadString();
|
||||
_loginIPs[i] = reader.ReadIPAddress();
|
||||
}
|
||||
}
|
||||
|
||||
_totalGameTime = reader.ReadTimeSpan();
|
||||
length = reader.ReadInt();
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
reader.ReadString(); // IP Restrictions
|
||||
}
|
||||
|
||||
if (version > 1)
|
||||
{
|
||||
_email = reader.ReadString();
|
||||
}
|
||||
_totalGameTime = reader.ReadTimeSpan();
|
||||
|
||||
if (version > 1)
|
||||
{
|
||||
_email = reader.ReadString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,163 +0,0 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using Server.Network;
|
||||
|
||||
namespace Server.Accounting
|
||||
{
|
||||
public static class AccountAttackLimiter
|
||||
{
|
||||
public static bool Enabled;
|
||||
|
||||
private static readonly List<InvalidAccountAccessLog> m_List = new();
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
Enabled = ServerConfiguration.GetOrUpdateSetting("accountAttackLimiter.enable", true);
|
||||
}
|
||||
|
||||
public static unsafe void Initialize()
|
||||
{
|
||||
if (!Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IncomingPackets.RegisterThrottler(0x80, &Throttle);
|
||||
IncomingPackets.RegisterThrottler(0x91, &Throttle);
|
||||
IncomingPackets.RegisterThrottler(0xCF, &Throttle);
|
||||
}
|
||||
|
||||
public static bool Throttle(int packetId, NetState ns, out bool drop)
|
||||
{
|
||||
var accessLog = FindAccessLog(ns);
|
||||
|
||||
if (accessLog == null)
|
||||
{
|
||||
drop = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
var date = Core.Now;
|
||||
var access = accessLog.LastAccessTime + ComputeThrottle(accessLog.Counts);
|
||||
var allow = date >= access;
|
||||
drop = !allow;
|
||||
return allow;
|
||||
}
|
||||
|
||||
public static InvalidAccountAccessLog FindAccessLog(NetState ns)
|
||||
{
|
||||
if (ns == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var ipAddress = ns.Address;
|
||||
|
||||
for (var i = 0; i < m_List.Count; ++i)
|
||||
{
|
||||
var accessLog = m_List[i];
|
||||
|
||||
if (accessLog.HasExpired)
|
||||
{
|
||||
m_List.RemoveAt(i--);
|
||||
}
|
||||
else if (accessLog.Address.Equals(ipAddress))
|
||||
{
|
||||
return accessLog;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void RegisterInvalidAccess(NetState ns)
|
||||
{
|
||||
if (ns == null || !Enabled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var accessLog = FindAccessLog(ns);
|
||||
|
||||
if (accessLog == null)
|
||||
{
|
||||
m_List.Add(accessLog = new InvalidAccountAccessLog(ns.Address));
|
||||
}
|
||||
|
||||
accessLog.Counts += 1;
|
||||
accessLog.RefreshAccessTime();
|
||||
|
||||
if (accessLog.Counts >= 3)
|
||||
{
|
||||
try
|
||||
{
|
||||
using var op = new StreamWriter("throttle.log", true);
|
||||
op.WriteLine(
|
||||
"{0}\t{1}\t{2}",
|
||||
Core.Now,
|
||||
ns,
|
||||
accessLog.Counts
|
||||
);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static TimeSpan ComputeThrottle(int counts)
|
||||
{
|
||||
if (counts >= 15)
|
||||
{
|
||||
return TimeSpan.FromMinutes(5.0);
|
||||
}
|
||||
|
||||
if (counts >= 10)
|
||||
{
|
||||
return TimeSpan.FromMinutes(1.0);
|
||||
}
|
||||
|
||||
if (counts >= 5)
|
||||
{
|
||||
return TimeSpan.FromSeconds(20.0);
|
||||
}
|
||||
|
||||
if (counts >= 3)
|
||||
{
|
||||
return TimeSpan.FromSeconds(10.0);
|
||||
}
|
||||
|
||||
if (counts >= 1)
|
||||
{
|
||||
return TimeSpan.FromSeconds(2.0);
|
||||
}
|
||||
|
||||
return TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public class InvalidAccountAccessLog
|
||||
{
|
||||
public InvalidAccountAccessLog(IPAddress address)
|
||||
{
|
||||
Address = address;
|
||||
RefreshAccessTime();
|
||||
}
|
||||
|
||||
public IPAddress Address { get; set; }
|
||||
|
||||
public DateTime LastAccessTime { get; set; }
|
||||
|
||||
public bool HasExpired => Core.Now >= LastAccessTime + TimeSpan.FromHours(1.0);
|
||||
|
||||
public int Counts { get; set; }
|
||||
|
||||
public void RefreshAccessTime()
|
||||
{
|
||||
LastAccessTime = Core.Now;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -369,11 +369,6 @@ public static class AccountHandler
|
|||
|
||||
acct.LogAccess(e.State);
|
||||
}
|
||||
|
||||
if (!e.Accepted)
|
||||
{
|
||||
AccountAttackLimiter.RegisterInvalidAccess(e.State);
|
||||
}
|
||||
}
|
||||
|
||||
public static void EventSink_GameLogin(GameLoginEventArgs e)
|
||||
|
|
@ -409,11 +404,6 @@ public static class AccountHandler
|
|||
e.Accepted = true;
|
||||
e.CityInfo = CharacterCreation.GetStartingCities(acct.Young);
|
||||
}
|
||||
|
||||
if (!e.Accepted)
|
||||
{
|
||||
AccountAttackLimiter.RegisterInvalidAccess(e.State);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool CheckAccount(Mobile mobCheck, Mobile accCheck)
|
||||
|
|
|
|||
|
|
@ -36,7 +36,6 @@ namespace Server.Gumps
|
|||
AccountDetails_Characters,
|
||||
AccountDetails_Access,
|
||||
AccountDetails_Access_ClientIPs,
|
||||
AccountDetails_Access_Restrictions,
|
||||
AccountDetails_Comments,
|
||||
AccountDetails_Tags,
|
||||
AccountDetails_ChangePassword,
|
||||
|
|
@ -132,7 +131,6 @@ namespace Server.Gumps
|
|||
AdminGumpPage.AccountDetails_Characters,
|
||||
AdminGumpPage.AccountDetails_Access,
|
||||
AdminGumpPage.AccountDetails_Access_ClientIPs,
|
||||
AdminGumpPage.AccountDetails_Access_Restrictions,
|
||||
AdminGumpPage.AccountDetails_Comments,
|
||||
AdminGumpPage.AccountDetails_Tags,
|
||||
AdminGumpPage.AccountDetails_ChangeAccess,
|
||||
|
|
@ -801,8 +799,7 @@ namespace Server.Gumps
|
|||
GetButtonID(5, 13),
|
||||
"Access",
|
||||
AdminGumpPage.AccountDetails_Access,
|
||||
AdminGumpPage.AccountDetails_Access_ClientIPs,
|
||||
AdminGumpPage.AccountDetails_Access_Restrictions
|
||||
AdminGumpPage.AccountDetails_Access_ClientIPs
|
||||
);
|
||||
AddPageButton(190, 70, GetButtonID(5, 2), "Comments", AdminGumpPage.AccountDetails_Comments);
|
||||
AddPageButton(190, 90, GetButtonID(5, 3), "Tags", AdminGumpPage.AccountDetails_Tags);
|
||||
|
|
@ -968,13 +965,6 @@ namespace Server.Gumps
|
|||
"View client addresses",
|
||||
AdminGumpPage.AccountDetails_Access_ClientIPs
|
||||
);
|
||||
AddPageButton(
|
||||
20,
|
||||
170,
|
||||
GetButtonID(5, 15),
|
||||
"Manage restrictions",
|
||||
AdminGumpPage.AccountDetails_Access_Restrictions
|
||||
);
|
||||
|
||||
goto case AdminGumpPage.AccountDetails;
|
||||
}
|
||||
|
|
@ -1038,67 +1028,6 @@ namespace Server.Gumps
|
|||
AddButton(190, 242 + i * 22, 0xFB1, 0xFB3, GetButtonID(10, index));
|
||||
}
|
||||
|
||||
goto case AdminGumpPage.AccountDetails_Access;
|
||||
}
|
||||
case AdminGumpPage.AccountDetails_Access_Restrictions:
|
||||
{
|
||||
if (state is not Account a)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
m_List ??= a.IpRestrictions.ToList<object>();
|
||||
|
||||
AddHtml(10, 195, 400, 20, "Address Restrictions".Center(LabelColor32));
|
||||
|
||||
AddTextField(227, 225, 120, 20, 0);
|
||||
|
||||
AddButtonLabeled(352, 225, GetButtonID(5, 19), "Add");
|
||||
|
||||
AddHtml(
|
||||
225,
|
||||
255,
|
||||
180,
|
||||
120,
|
||||
"Any clients connecting from an address not in this list will be rejected. Or, if the list is empty, any client may connect.".Color(
|
||||
LabelColor32
|
||||
)
|
||||
);
|
||||
|
||||
AddImageTiled(15, 219, 206, 156, 0xBBC);
|
||||
AddBlackAlpha(16, 220, 204, 154);
|
||||
|
||||
AddHtml(18, 221, 114, 20, "IP Address".Color(LabelColor32));
|
||||
|
||||
if (listPage > 0)
|
||||
{
|
||||
AddButton(184, 223, 0x15E3, 0x15E7, GetButtonID(1, 0));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddImage(184, 223, 0x25EA);
|
||||
}
|
||||
|
||||
if ((listPage + 1) * 6 < m_List.Count)
|
||||
{
|
||||
AddButton(201, 223, 0x15E1, 0x15E5, GetButtonID(1, 1));
|
||||
}
|
||||
else
|
||||
{
|
||||
AddImage(201, 223, 0x25E6);
|
||||
}
|
||||
|
||||
if (m_List.Count == 0)
|
||||
{
|
||||
AddHtml(18, 243, 200, 60, "There are no addresses in this list.".Color(LabelColor32));
|
||||
}
|
||||
|
||||
for (int i = 0, index = listPage * 6; i < 6 && index >= 0 && index < m_List.Count; ++i, ++index)
|
||||
{
|
||||
AddHtml(18, 243 + i * 22, 114, 20, ((string)m_List[index]).Color(LabelColor32));
|
||||
AddButton(190, 242 + i * 22, 0xFB1, 0xFB3, GetButtonID(8, index));
|
||||
}
|
||||
|
||||
goto case AdminGumpPage.AccountDetails_Access;
|
||||
}
|
||||
case AdminGumpPage.AccountDetails_Characters:
|
||||
|
|
@ -2733,20 +2662,6 @@ namespace Server.Gumps
|
|||
);
|
||||
break;
|
||||
}
|
||||
case 15:
|
||||
{
|
||||
from.SendGump(
|
||||
new AdminGump(
|
||||
from,
|
||||
AdminGumpPage.AccountDetails_Access_Restrictions,
|
||||
0,
|
||||
null,
|
||||
null,
|
||||
m_State
|
||||
)
|
||||
);
|
||||
break;
|
||||
}
|
||||
case 4:
|
||||
{
|
||||
from.Prompt = new AddCommentPrompt(m_State as Account);
|
||||
|
|
@ -3101,65 +3016,6 @@ namespace Server.Gumps
|
|||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case 19: // add
|
||||
{
|
||||
if (m_State is not Account a)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var ip = info.GetTextEntry(0)?.Trim();
|
||||
|
||||
string notice;
|
||||
|
||||
if (string.IsNullOrEmpty(ip))
|
||||
{
|
||||
notice = "You must enter an address to add.";
|
||||
}
|
||||
else
|
||||
{
|
||||
var list = a.IpRestrictions;
|
||||
|
||||
var contains = false;
|
||||
for (var i = 0; !contains && i < list.Length; ++i)
|
||||
{
|
||||
contains = list[i] == ip;
|
||||
}
|
||||
|
||||
if (contains)
|
||||
{
|
||||
notice = "That address is already contained in the list.";
|
||||
}
|
||||
else
|
||||
{
|
||||
var newList = new string[list.Length + 1];
|
||||
|
||||
for (var i = 0; i < list.Length; ++i)
|
||||
{
|
||||
newList[i] = list[i];
|
||||
}
|
||||
|
||||
newList[list.Length] = ip;
|
||||
|
||||
a.IpRestrictions = newList;
|
||||
|
||||
notice = $"{ip} : Added to restriction list.";
|
||||
}
|
||||
}
|
||||
|
||||
from.SendGump(
|
||||
new AdminGump(
|
||||
from,
|
||||
AdminGumpPage.AccountDetails_Access_Restrictions,
|
||||
0,
|
||||
null,
|
||||
notice,
|
||||
m_State
|
||||
)
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
case 20: // Change access level
|
||||
|
|
@ -3957,23 +3813,6 @@ namespace Server.Gumps
|
|||
)
|
||||
);
|
||||
}
|
||||
else if (m_PageType == AdminGumpPage.AccountDetails_Access_Restrictions)
|
||||
{
|
||||
var list = a.IpRestrictions.ToList();
|
||||
list.Remove(m_List[index] as string);
|
||||
a.IpRestrictions = list.ToArray();
|
||||
|
||||
from.SendGump(
|
||||
new AdminGump(
|
||||
from,
|
||||
AdminGumpPage.AccountDetails_Access_Restrictions,
|
||||
0,
|
||||
null,
|
||||
$"{m_List[index]} : Removed from list.",
|
||||
a
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
|
|
|||
115
Projects/UOContent/Migrations/Server.Accounting.Account.v6.json
generated
Normal file
115
Projects/UOContent/Migrations/Server.Accounting.Account.v6.json
generated
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
{
|
||||
"version": 6,
|
||||
"type": "Server.Accounting.Account",
|
||||
"properties": [
|
||||
{
|
||||
"name": "Username",
|
||||
"type": "string",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
"InternString"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "PasswordAlgorithm",
|
||||
"type": "Server.Accounting.Security.PasswordProtectionAlgorithm",
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Password",
|
||||
"type": "string",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "AccessLevel",
|
||||
"type": "Server.AccessLevel",
|
||||
"rule": "EnumMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Flags",
|
||||
"type": "int",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "LastLogin",
|
||||
"type": "System.DateTime",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "TotalGold",
|
||||
"type": "int",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "TotalPlat",
|
||||
"type": "int",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Mobiles",
|
||||
"type": "Server.Mobile[]",
|
||||
"rule": "ArrayMigrationRule",
|
||||
"ruleArguments": [
|
||||
"Server.Mobile",
|
||||
"SerializableInterfaceMigrationRule"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Comments",
|
||||
"type": "System.Collections.Generic.List\u003CServer.Accounting.AccountComment\u003E",
|
||||
"rule": "ListMigrationRule",
|
||||
"ruleArguments": [
|
||||
"Server.Accounting.AccountComment",
|
||||
"SerializationMethodSignatureMigrationRule",
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Tags",
|
||||
"type": "System.Collections.Generic.List\u003CServer.Accounting.AccountTag\u003E",
|
||||
"rule": "ListMigrationRule",
|
||||
"ruleArguments": [
|
||||
"Server.Accounting.AccountTag",
|
||||
"SerializationMethodSignatureMigrationRule",
|
||||
""
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "LoginIPs",
|
||||
"type": "System.Net.IPAddress[]",
|
||||
"rule": "ArrayMigrationRule",
|
||||
"ruleArguments": [
|
||||
"System.Net.IPAddress",
|
||||
"PrimitiveTypeMigrationRule"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "TotalGameTime",
|
||||
"type": "System.TimeSpan",
|
||||
"rule": "PrimitiveTypeMigrationRule"
|
||||
},
|
||||
{
|
||||
"name": "Email",
|
||||
"type": "string",
|
||||
"rule": "PrimitiveTypeMigrationRule",
|
||||
"ruleArguments": [
|
||||
""
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -2,147 +2,140 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using Server.Json;
|
||||
using Server.Mobiles;
|
||||
|
||||
namespace Server.Network
|
||||
namespace Server.Network;
|
||||
|
||||
public static class PacketThrottles
|
||||
{
|
||||
public static class PacketThrottles
|
||||
// Delay in milliseconds
|
||||
private static readonly int[] Delays = new int[0x100];
|
||||
private const string ThrottlesConfiguration = "Configuration/throttles.json";
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
// Delay in milliseconds
|
||||
private static readonly int[] Delays = new int[0x100];
|
||||
private const string ThrottlesConfiguration = "Configuration/throttles.json";
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
CommandSystem.Register("GetThrottle", AccessLevel.Administrator, GetThrottle);
|
||||
CommandSystem.Register("SetThrottle", AccessLevel.Administrator, SetThrottle);
|
||||
}
|
||||
|
||||
public static unsafe void Initialize()
|
||||
{
|
||||
var path = Path.Join(Core.BaseDirectory, ThrottlesConfiguration);
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var throttles = JsonConfig.Deserialize<SortedDictionary<string, int>>(path);
|
||||
foreach (var (k, v) in throttles)
|
||||
{
|
||||
if (!Utility.ToInt32(k, out var packetId))
|
||||
{
|
||||
Utility.PushColor(ConsoleColor.DarkYellow);
|
||||
Console.WriteLine("Packet Throttles: Error deserializing {0} from {1}", k, ThrottlesConfiguration);
|
||||
Utility.PopColor();
|
||||
continue;
|
||||
}
|
||||
|
||||
Delays[packetId] = v;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Delays[0x03] = 25; // Speech
|
||||
Delays[0xAD] = 25; // Speech
|
||||
Delays[0x12] = 25; // Text Commands
|
||||
Delays[0x75] = 500; // Rename request
|
||||
}
|
||||
|
||||
for (int i = 0; i < 0x100; i++)
|
||||
{
|
||||
if (Delays[i] > 0)
|
||||
{
|
||||
IncomingPackets.RegisterThrottler(i, &Throttle);
|
||||
}
|
||||
}
|
||||
|
||||
SaveDelays();
|
||||
}
|
||||
|
||||
[Usage("GetThrottle <packetID>")]
|
||||
[Description("Gets throttle for the given packet.")]
|
||||
public static void GetThrottle(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length != 1)
|
||||
{
|
||||
e.Mobile.SendMessage("Invalid Command Format. Should be [GetThrottle <packetID>");
|
||||
return;
|
||||
}
|
||||
|
||||
int packetID = e.GetInt32(0);
|
||||
|
||||
if (packetID is < 0 or > 0x100)
|
||||
{
|
||||
e.Mobile.SendMessage("Invalid Command Format. PacketID must be between 0 and 0x100.");
|
||||
return;
|
||||
}
|
||||
|
||||
e.Mobile.SendMessage($"Packet 0x{packetID:X} throttle is currently {Delays[packetID]}ms.");
|
||||
}
|
||||
|
||||
[Usage("SetThrottle <packetID> <timeInMilliseconds>")]
|
||||
[Description("Sets a throttle for the given packet.")]
|
||||
public static unsafe void SetThrottle(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length != 2)
|
||||
{
|
||||
e.Mobile.SendMessage("Invalid Command Format. Should be [SetThrottle <packetID> <timeInMilliseconds>");
|
||||
return;
|
||||
}
|
||||
|
||||
int packetID = e.GetInt32(0);
|
||||
int delay = e.GetInt32(1);
|
||||
|
||||
if (packetID is < 0 or > 0x100)
|
||||
{
|
||||
e.Mobile.SendMessage("Invalid Command Format. PacketID must be between 0 and 0x100.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (delay > 5000)
|
||||
{
|
||||
e.Mobile.SendMessage("Invalid Command Format. Delay cannot exceed 5000 milliseconds.");
|
||||
return;
|
||||
}
|
||||
|
||||
long oldDelay = Delays[packetID];
|
||||
|
||||
if (oldDelay == 0 && delay > 0)
|
||||
{
|
||||
IncomingPackets.RegisterThrottler(packetID, &Throttle);
|
||||
e.Mobile.SendMessage($"Set throttle for packet 0x{packetID:X2} to {delay}ms.");
|
||||
}
|
||||
else if (oldDelay > 0 && delay == 0)
|
||||
{
|
||||
IncomingPackets.RegisterThrottler(packetID, null);
|
||||
e.Mobile.SendMessage($"Removed throttle for packet 0x{packetID:X2}");
|
||||
}
|
||||
|
||||
Delays[packetID] = delay;
|
||||
SaveDelays();
|
||||
}
|
||||
|
||||
private static void SaveDelays()
|
||||
{
|
||||
SortedDictionary<string, int> table = new();
|
||||
for (var i = 0; i < Delays.Length; i++)
|
||||
{
|
||||
var delay = Delays[i];
|
||||
|
||||
if (delay != 0)
|
||||
{
|
||||
table[$"0x{i:X2}"] = delay;
|
||||
}
|
||||
}
|
||||
|
||||
var path = Path.Join(Core.BaseDirectory, ThrottlesConfiguration);
|
||||
JsonConfig.Serialize(path, table);
|
||||
}
|
||||
|
||||
public static bool Throttle(int packetID, NetState ns, out bool drop)
|
||||
{
|
||||
drop = ns.Mobile is PlayerMobile { AccessLevel: < AccessLevel.Counselor }
|
||||
&& Core.TickCount - ns.GetPacketTime(packetID) < Delays[packetID];
|
||||
|
||||
return !drop;
|
||||
}
|
||||
CommandSystem.Register("GetThrottle", AccessLevel.Administrator, GetThrottle);
|
||||
CommandSystem.Register("SetThrottle", AccessLevel.Administrator, SetThrottle);
|
||||
}
|
||||
|
||||
public static unsafe void Initialize()
|
||||
{
|
||||
var path = Path.Join(Core.BaseDirectory, ThrottlesConfiguration);
|
||||
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var throttles = JsonConfig.Deserialize<SortedDictionary<string, int>>(path);
|
||||
foreach (var (k, v) in throttles)
|
||||
{
|
||||
if (!Utility.ToInt32(k, out var packetId))
|
||||
{
|
||||
Utility.PushColor(ConsoleColor.DarkYellow);
|
||||
Console.WriteLine("Packet Throttles: Error deserializing {0} from {1}", k, ThrottlesConfiguration);
|
||||
Utility.PopColor();
|
||||
continue;
|
||||
}
|
||||
|
||||
Delays[packetId] = v;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Delays[0x03] = 25; // Speech
|
||||
Delays[0xAD] = 25; // Speech
|
||||
Delays[0x12] = 25; // Text Commands
|
||||
Delays[0x75] = 500; // Rename request
|
||||
}
|
||||
|
||||
for (int i = 0; i < 0x100; i++)
|
||||
{
|
||||
if (Delays[i] > 0)
|
||||
{
|
||||
IncomingPackets.RegisterThrottler(i, &Throttle);
|
||||
}
|
||||
}
|
||||
|
||||
SaveDelays();
|
||||
}
|
||||
|
||||
[Usage("GetThrottle <packetID>")]
|
||||
[Description("Gets throttle for the given packet.")]
|
||||
public static void GetThrottle(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length != 1)
|
||||
{
|
||||
e.Mobile.SendMessage("Invalid Command Format. Should be [GetThrottle <packetID>");
|
||||
return;
|
||||
}
|
||||
|
||||
int packetID = e.GetInt32(0);
|
||||
|
||||
if (packetID is < 0 or > 0x100)
|
||||
{
|
||||
e.Mobile.SendMessage("Invalid Command Format. PacketID must be between 0 and 0x100.");
|
||||
return;
|
||||
}
|
||||
|
||||
e.Mobile.SendMessage($"Packet 0x{packetID:X} throttle is currently {Delays[packetID]}ms.");
|
||||
}
|
||||
|
||||
[Usage("SetThrottle <packetID> <timeInMilliseconds>")]
|
||||
[Description("Sets a throttle for the given packet.")]
|
||||
public static unsafe void SetThrottle(CommandEventArgs e)
|
||||
{
|
||||
if (e.Length != 2)
|
||||
{
|
||||
e.Mobile.SendMessage("Invalid Command Format. Should be [SetThrottle <packetID> <timeInMilliseconds>");
|
||||
return;
|
||||
}
|
||||
|
||||
int packetID = e.GetInt32(0);
|
||||
int delay = e.GetInt32(1);
|
||||
|
||||
if (packetID is < 0 or > 0x100)
|
||||
{
|
||||
e.Mobile.SendMessage("Invalid Command Format. PacketID must be between 0 and 0x100.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (delay > 5000)
|
||||
{
|
||||
e.Mobile.SendMessage("Invalid Command Format. Delay cannot exceed 5000 milliseconds.");
|
||||
return;
|
||||
}
|
||||
|
||||
long oldDelay = Delays[packetID];
|
||||
|
||||
if (oldDelay == 0 && delay > 0)
|
||||
{
|
||||
IncomingPackets.RegisterThrottler(packetID, &Throttle);
|
||||
e.Mobile.SendMessage($"Set throttle for packet 0x{packetID:X2} to {delay}ms.");
|
||||
}
|
||||
else if (oldDelay > 0 && delay == 0)
|
||||
{
|
||||
IncomingPackets.RegisterThrottler(packetID, null);
|
||||
e.Mobile.SendMessage($"Removed throttle for packet 0x{packetID:X2}");
|
||||
}
|
||||
|
||||
Delays[packetID] = delay;
|
||||
SaveDelays();
|
||||
}
|
||||
|
||||
private static void SaveDelays()
|
||||
{
|
||||
SortedDictionary<string, int> table = new();
|
||||
for (var i = 0; i < Delays.Length; i++)
|
||||
{
|
||||
var delay = Delays[i];
|
||||
|
||||
if (delay != 0)
|
||||
{
|
||||
table[$"0x{i:X2}"] = delay;
|
||||
}
|
||||
}
|
||||
|
||||
var path = Path.Join(Core.BaseDirectory, ThrottlesConfiguration);
|
||||
JsonConfig.Serialize(path, table);
|
||||
}
|
||||
|
||||
public static bool Throttle(int packetId, NetState ns) =>
|
||||
ns.Mobile is { AccessLevel: AccessLevel.Player } && Core.TickCount - ns.GetPacketTime(packetId) < Delays[packetId];
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue