From 75db56edc4a4083cc86d23404d7f4ba4e1a5d93f Mon Sep 17 00:00:00 2001 From: Kamron Batman <3953314+kamronbatman@users.noreply.github.com> Date: Sat, 5 Jun 2021 19:39:16 -0700 Subject: [PATCH] fix(core): Fixes accounts and moves it to codegen (#644) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - [X] Fixes TimeSpan not working with codegen - [X] Fixes bad check for generic classes with a serialize method and deserialize ctor - [X] Moves Accounts to codegen so it is versioned - [X] Fixes deserialization of old Accounts with no version variable. - [X] Fixes deserialize seek not doing anything. 🙈 - [X] Adds Email to serialization --- ...lizableEntityGeneration.SerializeMethod.cs | 3 +- .../Rules/PrimitiveTypeMigrationRule.cs | 6 +- ...rializationMethodSignatureMigrationRule.cs | 3 +- .../SymbolMetadata/SymbolMetadata.Builtin.cs | 7 + .../SymbolMetadata/SymbolMetadata.UO.cs | 6 +- Projects/Server/IAccount.cs | 2 +- Projects/Server/Serialization/BufferReader.cs | 2 + Projects/UOContent/Accounting/Account.cs | 652 +++++++----------- .../UOContent/Accounting/AccountComment.cs | 17 - Projects/UOContent/Accounting/AccountTag.cs | 12 - Projects/UOContent/Accounting/Accounts.cs | 14 +- Projects/UOContent/Gumps/AdminGump.cs | 10 +- .../Server.Accounting.Account.v2.json | 108 +++ 13 files changed, 378 insertions(+), 464 deletions(-) create mode 100644 Projects/UOContent/Migrations/Server.Accounting.Account.v2.json diff --git a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs index 4a22fb723..8bed1941e 100644 --- a/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs +++ b/Projects/SerializationGenerator/SerializableEntityGeneration/SerializableEntityGeneration.SerializeMethod.cs @@ -44,12 +44,11 @@ namespace SerializationGenerator if (isOverride) { - source.AppendLine(); source.AppendLine($"{indent}base.Serialize(writer);"); + source.AppendLine(); } // Version - source.AppendLine(); source.AppendLine($"{indent}writer.{(encodedVersion ? "WriteEncodedInt" : "Write")}(_version);"); foreach (var property in properties) diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs index 567cf1ffb..391e46130 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/PrimitiveTypeMigrationRule.cs @@ -34,7 +34,7 @@ namespace SerializableMigration out string[] ruleArguments ) { - if (symbol.IsIpAddress(compilation)) + if (symbol.IsIpAddress(compilation) || symbol.IsTimeSpan(compilation)) { ruleArguments = Array.Empty(); return true; @@ -91,6 +91,7 @@ namespace SerializableMigration var argument = property.RuleArguments.Length >= 1 ? property.RuleArguments[0] : null; const string ipAddress = SymbolMetadata.IPADDRESS_CLASS; + const string timeSpan = SymbolMetadata.TIMESPAN_STRUCT; const string date = "System.DateTime"; var readMethod = property.Type switch @@ -111,7 +112,8 @@ namespace SerializableMigration "decimal" => "ReadDecimal", date when argument == "DeltaTime" => "ReadDeltaTime", date => "ReadDateTime", - ipAddress => "ReadIPAddress" + ipAddress => "ReadIPAddress", + timeSpan => "ReadTimeSpan" }; var readArgument = readMethod == "ReadString" && argument == "InternString" ? "true" : ""; diff --git a/Projects/SerializationGenerator/SerializableMigration/Rules/SerializationMethodSignatureMigrationRule.cs b/Projects/SerializationGenerator/SerializableMigration/Rules/SerializationMethodSignatureMigrationRule.cs index 211d9d1eb..5d74955a1 100644 --- a/Projects/SerializationGenerator/SerializableMigration/Rules/SerializationMethodSignatureMigrationRule.cs +++ b/Projects/SerializationGenerator/SerializableMigration/Rules/SerializationMethodSignatureMigrationRule.cs @@ -15,6 +15,7 @@ using System; using System.Collections.Immutable; +using System.Linq; using System.Text; using Microsoft.CodeAnalysis; using SerializationGenerator; @@ -63,7 +64,7 @@ namespace SerializableMigration var argument = property.RuleArguments.Length >= 1 && property.RuleArguments[0] == "DeserializationRequiresParent" ? ", this" : ""; - source.AppendLine($"{indent}{propertyName} = new {property.Type}(reader{argument})"); + source.AppendLine($"{indent}{propertyName} = new {property.Type}(reader{argument});"); } public void GenerateSerializationMethod(StringBuilder source, string indent, SerializableProperty property) diff --git a/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.Builtin.cs b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.Builtin.cs index 1ae6fa54b..541a114b6 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.Builtin.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.Builtin.cs @@ -23,6 +23,13 @@ namespace SerializationGenerator public const string HASHSET_CLASS = "System.Collections.Generic.HashSet`1"; public const string IPADDRESS_CLASS = "System.Net.IPAddress"; public const string KEYVALUEPAIR_STRUCT = "System.Collections.Generic.KeyValuePair"; + public const string TIMESPAN_STRUCT = "System.TimeSpan"; + + public static bool IsTimeSpan(this ISymbol symbol, Compilation compilation) => + symbol.Equals( + compilation.GetTypeByMetadataName(TIMESPAN_STRUCT), + SymbolEqualityComparer.Default + ); public static bool IsIpAddress(this ISymbol symbol, Compilation compilation) => symbol.Equals( diff --git a/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs index 553857a41..72a091dfb 100644 --- a/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs +++ b/Projects/SerializationGenerator/SourceGeneration/SymbolMetadata/SymbolMetadata.UO.cs @@ -73,10 +73,10 @@ namespace SerializationGenerator m => !m.IsStatic && m.MethodKind == MethodKind.Constructor && m.Parameters.Length <= 2 && - m.Parameters[0].Equals(genericReaderInterface, SymbolEqualityComparer.Default) + SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, genericReaderInterface) ); - requiresParent = genericCtor?.Parameters.Length == 2 && genericCtor.Parameters[1].Equals(symbol, SymbolEqualityComparer.Default); + requiresParent = genericCtor?.Parameters.Length == 2 && SymbolEqualityComparer.Default.Equals(genericCtor.Parameters[1].Type, symbol); return genericCtor != null; } @@ -98,7 +98,7 @@ namespace SerializationGenerator m => !m.IsStatic && m.ReturnsVoid && m.Parameters.Length == 1 && - m.Parameters[0].Equals(genericWriterInterface, SymbolEqualityComparer.Default) && + SymbolEqualityComparer.Default.Equals(m.Parameters[0].Type, genericWriterInterface) && m.DeclaredAccessibility == Accessibility.Public ); } diff --git a/Projects/Server/IAccount.cs b/Projects/Server/IAccount.cs index e141289a1..04575082f 100644 --- a/Projects/Server/IAccount.cs +++ b/Projects/Server/IAccount.cs @@ -95,7 +95,7 @@ namespace Server.Accounting long GetTotalGold(); } - public interface IAccount : IGoldAccount, IComparable, ISerializable + public interface IAccount : IGoldAccount, IComparable { string Username { get; set; } string Email { get; set; } diff --git a/Projects/Server/Serialization/BufferReader.cs b/Projects/Server/Serialization/BufferReader.cs index de0d3208b..806956b27 100644 --- a/Projects/Server/Serialization/BufferReader.cs +++ b/Projects/Server/Serialization/BufferReader.cs @@ -164,6 +164,8 @@ namespace Server throw new ArgumentException($"BufferReader does not support {nameof(offset)} beyond Int32.MaxValue"); } + _position = (int)position; + return _position; } } diff --git a/Projects/UOContent/Accounting/Account.cs b/Projects/UOContent/Accounting/Account.cs index 7a9b6c1d4..cf2da5115 100644 --- a/Projects/UOContent/Accounting/Account.cs +++ b/Projects/UOContent/Accounting/Account.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.IO; using System.Net; using System.Xml; using Server.Accounting.Security; @@ -10,57 +11,137 @@ using Server.Network; namespace Server.Accounting { - public partial class Account : IAccount, IComparable + [Serializable(2)] + public partial class Account : IAccount, IComparable, ISerializable { public static readonly TimeSpan YoungDuration = TimeSpan.FromHours(40.0); public static readonly TimeSpan InactiveDuration = TimeSpan.FromDays(180.0); public static readonly TimeSpan EmptyInactiveDuration = TimeSpan.FromDays(30.0); - private Mobile[] m_Mobiles; - private AccessLevel m_AccessLevel; - private List m_Comments; - private PasswordProtectionAlgorithm m_PasswordAlgorithm; - private List m_Tags; - private TimeSpan m_TotalGameTime; + [SerializableField(0)] + private string _username; + + [SerializableField(1)] + private PasswordProtectionAlgorithm _passwordAlgorithm; + + [SerializableField(2)] + private string _password; + + [SerializableField(3)] + private AccessLevel _accessLevel; + + [SerializableField(4)] + private int _flags; + + [SerializableField(5, setter: "private")] + private DateTime _created; + + [SerializableField(6)] + private DateTime _lastLogin; + + /// + /// This amount represents the current amount of Gold owned by the player. + /// The value does not include the value of Platinum and ranges from + /// 0 to 999,999,999 by default. + /// + [SerializableField(7, setter: "private")] + [SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")] + public int _totalGold; + + /// + /// This amount represents the current amount of Platinum owned by the player. + /// The value does not include the value of Gold and ranges from + /// 0 to 2,147,483,647 by default. + /// One Platinum represents the value of CurrencyThreshold in Gold. + /// + [SerializableField(8, setter: "private")] + [SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")] + public int _totalPlat; + + [SerializableField(9, "private", "private")] + private Mobile[] _mobiles; + + [SerializableField(10, setter: "private")] + private List _comments; + + [SerializableField(10, setter: "private")] + private List _tags; + + [SerializableField(11)] + private IPAddress[] _loginIPs; + + /// + /// List of IP addresses for restricted access. '*' wildcard supported. If the array contains zero entries, all IP addresses + /// are allowed. + /// + [SerializableField(12)] + private string[] _ipRestrictions; + + private TimeSpan _totalGameTime; + + /// + /// Gets the total game time of this account, also considering the game time of characters + /// that have been deleted. + /// + [SerializableField(13)] + public TimeSpan TotalGameTime + { + get + { + for (var i = 0; i < _mobiles.Length; i++) + { + if (_mobiles[i] is PlayerMobile m && m.NetState != null) + { + return _totalGameTime + (Core.Now - m.SessionStart); + } + } + + return _totalGameTime; + } + private set + { + _totalGameTime = value; + ((ISerializable)this).MarkDirty(); + } + } + + [SerializableField(14)] + [SerializableFieldAttr("[CommandProperty(AccessLevel.Administrator)]")] + private string _email; + private Timer m_YoungTimer; public Account(string username, string password) : this(Accounts.NewAccount) { - Username = username; + _username = username; SetPassword(password); - m_AccessLevel = AccessLevel.Player; + _accessLevel = AccessLevel.Player; - Created = LastLogin = Core.Now; - m_TotalGameTime = TimeSpan.Zero; + _created = _lastLogin = Core.Now; + _totalGameTime = TimeSpan.Zero; - m_Mobiles = new Mobile[7]; + _mobiles = new Mobile[7]; - IPRestrictions = Array.Empty(); - LoginIPs = Array.Empty(); + _ipRestrictions = Array.Empty(); + _loginIPs = Array.Empty(); Accounts.Add(this); ((ISerializable)this).MarkDirty(); } - public Account(Serial serial) - { - Serial = serial; - SetTypeRef(GetType()); - } - public Account(XmlElement node) { Serial = Accounts.NewAccount; SetTypeRef(GetType()); - Username = Utility.GetText(node["username"], "empty"); + _username = Utility.GetText(node["username"], "empty"); - Enum.TryParse(Utility.GetText(node["passwordAlgorithm"], null), true, out m_PasswordAlgorithm); + Enum.TryParse(Utility.GetText(node["passwordAlgorithm"], null), true, out _passwordAlgorithm); // Backward compatibility with RunUO/ServUO - if (m_PasswordAlgorithm == PasswordProtectionAlgorithm.None) + if (_passwordAlgorithm == PasswordProtectionAlgorithm.None) { var upgraded = UpgradePassword( @@ -78,44 +159,44 @@ namespace Server.Accounting } else { - Password = Utility.GetText(node["password"], null); + _password = Utility.GetText(node["password"], null); } - Enum.TryParse(Utility.GetText(node["accessLevel"], "Player"), true, out m_AccessLevel); - Flags = Utility.GetXMLInt32(Utility.GetText(node["flags"], "0"), 0); - Created = Utility.GetXMLDateTime(Utility.GetText(node["created"], null), Core.Now); - LastLogin = Utility.GetXMLDateTime(Utility.GetText(node["lastLogin"], null), Core.Now); + Enum.TryParse(Utility.GetText(node["accessLevel"], "Player"), true, out _accessLevel); + _flags = Utility.GetXMLInt32(Utility.GetText(node["flags"], "0"), 0); + _created = Utility.GetXMLDateTime(Utility.GetText(node["created"], null), Core.Now); + _lastLogin = Utility.GetXMLDateTime(Utility.GetText(node["lastLogin"], null), Core.Now); - TotalGold = Utility.GetXMLInt32(Utility.GetText(node["totalGold"], "0"), 0); - TotalPlat = Utility.GetXMLInt32(Utility.GetText(node["totalPlat"], "0"), 0); + _totalGold = Utility.GetXMLInt32(Utility.GetText(node["totalGold"], "0"), 0); + _totalPlat = Utility.GetXMLInt32(Utility.GetText(node["totalPlat"], "0"), 0); - m_Mobiles = LoadMobiles(node); - m_Comments = LoadComments(node); - m_Tags = LoadTags(node); - LoginIPs = LoadAddressList(node); - IPRestrictions = LoadAccessCheck(node); + _mobiles = LoadMobiles(node); + _comments = LoadComments(node); + _tags = LoadTags(node); + _loginIPs = LoadAddressList(node); + _ipRestrictions = LoadAccessCheck(node); - for (var i = 0; i < m_Mobiles.Length; ++i) + for (var i = 0; i < _mobiles.Length; ++i) { - if (m_Mobiles[i] != null) + if (_mobiles[i] != null) { - m_Mobiles[i].Account = this; + _mobiles[i].Account = this; } } var totalGameTime = Utility.GetXMLTimeSpan(Utility.GetText(node["totalGameTime"], null), TimeSpan.Zero); if (totalGameTime == TimeSpan.Zero) { - for (var i = 0; i < m_Mobiles.Length; i++) + for (var i = 0; i < _mobiles.Length; i++) { - if (m_Mobiles[i] is PlayerMobile m) + if (_mobiles[i] is PlayerMobile m) { totalGameTime += m.GameTime; } } } - m_TotalGameTime = totalGameTime; + _totalGameTime = totalGameTime; if (Young) { @@ -123,6 +204,7 @@ namespace Server.Accounting } Accounts.Add(this); + ((ISerializable)this).MarkDirty(); } public void SetTypeRef(Type type) @@ -141,37 +223,6 @@ namespace Server.Accounting /// public HardwareInfo HardwareInfo { get; set; } - /// - /// List of IP addresses for restricted access. '*' wildcard supported. If the array contains zero entries, all IP addresses - /// are allowed. - /// - public string[] IPRestrictions { get; set; } - - /// - /// List of IP addresses which have successfully logged into this account. - /// - public IPAddress[] LoginIPs { get; set; } - - /// - /// List of account comments. Type of contained objects is AccountComment. - /// - public List Comments => m_Comments ?? (m_Comments = new List()); - - /// - /// List of account tags. Type of contained objects is AccountTag. - /// - public List Tags => m_Tags ?? (m_Tags = new List()); - - /// - /// Account username and password. May be null. - /// - public string Password { get; set; } - - /// - /// Internal bitfield of account flags. Consider using direct access properties (Banned, Young), or GetFlag/SetFlag methods - /// - public int Flags { get; set; } - /// /// Gets or sets a flag indicating if this account is banned. /// @@ -216,16 +267,6 @@ namespace Server.Accounting } } - /// - /// The date and time of when this account was created. - /// - public DateTime Created { get; private set; } - - /// - /// Gets or sets the date and time when this account was last accessed. - /// - public DateTime LastLogin { get; set; } - /// /// An account is considered inactive based upon LastLogin and InactiveDuration. If the account is empty, it is based upon /// EmptyInactiveDuration @@ -239,168 +280,110 @@ namespace Server.Accounting return false; } - var inactiveLength = Core.Now - LastLogin; + var inactiveLength = Core.Now - _lastLogin; return inactiveLength > (Count == 0 ? EmptyInactiveDuration : InactiveDuration); } } - /// - /// Gets the total game time of this account, also considering the game time of characters - /// that have been deleted. - /// - public TimeSpan TotalGameTime - { - get - { - for (var i = 0; i < m_Mobiles.Length; i++) - { - if (m_Mobiles[i] is PlayerMobile m && m.NetState != null) - { - return m_TotalGameTime + (Core.Now - m.SessionStart); - } - } - - return m_TotalGameTime; - } - } - - long ISerializable.SavePosition { get; set; } - - BufferWriter ISerializable.SaveBuffer { get; set; } - public int TypeRef { get; private set; } public Serial Serial { get; set; } - public void Deserialize(IGenericReader reader) + [AfterDeserialization] + private void AfterDeserialization() { - Username = reader.ReadString(); - m_PasswordAlgorithm = (PasswordProtectionAlgorithm)reader.ReadInt(); - Password = reader.ReadString(); - m_AccessLevel = (AccessLevel)reader.ReadInt(); - Flags = reader.ReadInt(); - Created = reader.ReadDateTime(); - LastLogin = reader.ReadDateTime(); - - TotalGold = reader.ReadInt(); - TotalPlat = reader.ReadInt(); - - m_Mobiles = new Mobile[7]; - var length = reader.ReadInt(); - for (int i = 0; i < length; i++) + if (_comments.Count == 0) { - m_Mobiles[i] = reader.ReadEntity(); + _comments = null; } - length = reader.ReadInt(); - m_Comments = length > 0 ? new List(length) : null; - for (int i = 0; i < length; i++) + if (_tags.Count == 0) { - m_Comments!.Add(new AccountComment(reader)); + _tags = null; } - length = reader.ReadInt(); - m_Tags = length > 0 ? new List(length) : null; - for (int i = 0; i < length; i++) + for (var i = 0; i < _mobiles.Length; ++i) { - m_Tags!.Add(new AccountTag(reader)); - } - - length = reader.ReadInt(); - LoginIPs = new IPAddress[length]; - for (int i = 0; i < length; i++) - { - if (IPAddress.TryParse(reader.ReadString(), out var address)) + if (_mobiles[i] != null) { - LoginIPs[i] = Utility.Intern(address); + _mobiles[i].Account = this; } } - length = reader.ReadInt(); - IPRestrictions = new string[length]; - for (int i = 0; i < length; i++) + if (_totalGameTime == TimeSpan.Zero) { - IPRestrictions[i] = reader.ReadString(); - } - - for (var i = 0; i < m_Mobiles.Length; ++i) - { - if (m_Mobiles[i] != null) + for (var i = 0; i < _mobiles.Length; i++) { - m_Mobiles[i].Account = this; - } - } - - var totalGameTime = reader.ReadTimeSpan(); - if (totalGameTime == TimeSpan.Zero) - { - for (var i = 0; i < m_Mobiles.Length; i++) - { - if (m_Mobiles[i] is PlayerMobile m) + if (_mobiles[i] is PlayerMobile m) { - totalGameTime += m.GameTime; + _totalGameTime += m.GameTime; } } } - m_TotalGameTime = totalGameTime; - if (Young) { CheckYoung(); } } - public void Serialize(IGenericWriter writer) + // Handle old deserialization before codegen + private void Deserialize(IGenericReader reader, int version) { - writer.Write(Username); - writer.Write((int)m_PasswordAlgorithm); - writer.Write(Password); - writer.Write((int)m_AccessLevel); - writer.Write(Flags); - writer.Write(Created); - writer.Write(LastLogin); - writer.Write(TotalGold); - writer.Write(TotalPlat); + // 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); - writer.Write(Count); - for (int i = 0; i < m_Mobiles.Length; i++) + _username = reader.ReadString(); + _passwordAlgorithm = (PasswordProtectionAlgorithm)reader.ReadInt(); + _password = reader.ReadString(); + _accessLevel = (AccessLevel)reader.ReadInt(); + _flags = reader.ReadInt(); + _created = reader.ReadDateTime(); + _lastLogin = reader.ReadDateTime(); + + _totalGold = reader.ReadInt(); + _totalPlat = reader.ReadInt(); + + _mobiles = new Mobile[7]; + var length = reader.ReadInt(); + for (int i = 0; i < length; i++) { - var m = m_Mobiles[i]; - if (m != null) + _mobiles[i] = reader.ReadEntity(); + } + + length = reader.ReadInt(); + _comments = length > 0 ? new List(length) : null; + for (int i = 0; i < length; i++) + { + _comments!.Add(new AccountComment(reader)); + } + + length = reader.ReadInt(); + _tags = length > 0 ? new List(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 (IPAddress.TryParse(reader.ReadString(), out var address)) { - writer.Write(m); + _loginIPs[i] = Utility.Intern(address); } } - var length = m_Comments?.Count ?? 0; - writer.Write(length); + length = reader.ReadInt(); + _ipRestrictions = new string[length]; for (int i = 0; i < length; i++) { - m_Comments![i].Serialize(writer); + _ipRestrictions[i] = reader.ReadString(); } - length = m_Tags?.Count ?? 0; - writer.Write(length); - for (int i = 0; i < length; i++) - { - m_Tags![i].Serialize(writer); - } - - writer.Write(LoginIPs.Length); - for (int i = 0; i < LoginIPs.Length; i++) - { - writer.Write(LoginIPs[i].ToString()); - } - - writer.Write(IPRestrictions.Length); - for (int i = 0; i < IPRestrictions.Length; i++) - { - writer.Write(IPRestrictions[i]); - } - - writer.Write(TotalGameTime); + _totalGameTime = reader.ReadTimeSpan(); } /// @@ -427,12 +410,12 @@ namespace Server.Accounting m.Delete(); m.Account = null; - m_Mobiles[i] = null; + _mobiles[i] = null; } - if (LoginIPs.Length != 0 && AccountHandler.IPTable.ContainsKey(LoginIPs[0])) + if (_loginIPs.Length != 0 && AccountHandler.IPTable.ContainsKey(_loginIPs[0])) { - --AccountHandler.IPTable[LoginIPs[0]]; + --AccountHandler.IPTable[_loginIPs[0]]; } Deleted = true; @@ -441,45 +424,26 @@ namespace Server.Accounting public bool Deleted { get; private set; } - /// - /// Account username. Case insensitive validation. - /// - public string Username { get; set; } - - /// - /// Account email address. - /// - public string Email { get; set; } - - /// - /// Initial AccessLevel for new characters created on this account. - /// - public AccessLevel AccessLevel - { - get => m_AccessLevel; - set => m_AccessLevel = value; - } - public void SetPassword(string plainPassword) { Password = AccountSecurity.CurrentPasswordProtection.EncryptPassword(plainPassword); - m_PasswordAlgorithm = AccountSecurity.CurrentAlgorithm; + PasswordAlgorithm = AccountSecurity.CurrentAlgorithm; } public bool CheckPassword(string plainPassword) { - var phrase = m_PasswordAlgorithm == PasswordProtectionAlgorithm.SHA1 - ? $"{Username}{plainPassword}" + var phrase = _passwordAlgorithm == PasswordProtectionAlgorithm.SHA1 + ? $"{_username}{plainPassword}" : plainPassword; - var ok = AccountSecurity.GetPasswordProtection(m_PasswordAlgorithm).ValidatePassword(Password, phrase); + var ok = AccountSecurity.GetPasswordProtection(_passwordAlgorithm).ValidatePassword(Password, phrase); if (!ok) { return false; } // Upgrade the password protection in case we change the algorithm - if (m_PasswordAlgorithm != AccountSecurity.CurrentAlgorithm) + if (_passwordAlgorithm != AccountSecurity.CurrentAlgorithm) { SetPassword(plainPassword); } @@ -518,7 +482,7 @@ namespace Server.Accounting /// /// Gets the maximum amount of characters that this account can hold. /// - public int Length => m_Mobiles.Length; + public int Length => _mobiles.Length; /// /// Gets or sets the character at a specified index for this account. Out of bound index values are handled; null returned @@ -528,9 +492,9 @@ namespace Server.Accounting { get { - if (index >= 0 && index < m_Mobiles.Length) + if (index >= 0 && index < _mobiles.Length) { - var m = m_Mobiles[index]; + var m = _mobiles[index]; if (m?.Deleted != true) { @@ -540,25 +504,27 @@ namespace Server.Accounting // This is the only place that clears a mobile for garbage collection // outside of an entire account deletion. m.Account = null; - m_Mobiles[index] = null; + _mobiles[index] = null; + ((ISerializable)this).MarkDirty(); } return null; } set { - if (index >= 0 && index < m_Mobiles.Length) + if (index >= 0 && index < _mobiles.Length) { - if (m_Mobiles[index] != null) + if (_mobiles[index] != null) { - m_Mobiles[index].Account = null; + _mobiles[index].Account = null; } - m_Mobiles[index] = value; + _mobiles[index] = value; + ((ISerializable)this).MarkDirty(); - if (m_Mobiles[index] != null) + if (_mobiles[index] != null) { - m_Mobiles[index].Account = this; + _mobiles[index].Account = this; } } } @@ -566,23 +532,6 @@ namespace Server.Accounting public int CompareTo(IAccount other) => string.CompareOrdinal(Username, other?.Username); - /// - /// This amount represents the current amount of Gold owned by the player. - /// The value does not include the value of Platinum and ranges from - /// 0 to 999,999,999 by default. - /// - [CommandProperty(AccessLevel.Administrator)] - public int TotalGold { get; private set; } - - /// - /// This amount represents the current amount of Platinum owned by the player. - /// The value does not include the value of Gold and ranges from - /// 0 to 2,147,483,647 by default. - /// One Platinum represents the value of CurrencyThreshold in Gold. - /// - [CommandProperty(AccessLevel.Administrator)] - public int TotalPlat { get; private set; } - /// /// Attempts to deposit the given amount of Gold into this account. /// If the given amount is greater than the CurrencyThreshold, @@ -634,7 +583,7 @@ namespace Server.Accounting return true; } - if (amount > TotalGold) + if (amount > _totalGold) { return false; } @@ -656,7 +605,7 @@ namespace Server.Accounting return true; } - if (amount > TotalPlat) + if (amount > _totalPlat) { return false; } @@ -671,15 +620,15 @@ namespace Server.Accounting /// This is strictly for backwards compatibility /// /// Total gold, capped at Int32.MaxValue - public long GetTotalGold() => TotalGold + TotalPlat * AccountGold.CurrencyThreshold; + public long GetTotalGold() => _totalGold + _totalPlat * AccountGold.CurrencyThreshold; - public int CompareTo(Account other) => string.CompareOrdinal(Username, other?.Username); + public int CompareTo(Account other) => string.CompareOrdinal(_username, other?._username); /// /// Gets the value of a specific flag in the Flags bitfield. /// /// The zero-based flag index. - public bool GetFlag(int index) => (Flags & (1 << index)) != 0; + public bool GetFlag(int index) => (_flags & (1 << index)) != 0; /// /// Sets the value of a specific flag in the Flags bitfield. @@ -705,7 +654,8 @@ namespace Server.Accounting /// New tag value. public void AddTag(string name, string value) { - Tags.Add(new AccountTag(name, value)); + _tags.Add(new AccountTag(name, value)); + ((ISerializable)this).MarkDirty(); } /// @@ -714,18 +664,19 @@ namespace Server.Accounting /// Tag name to remove. public void RemoveTag(string name) { - for (var i = Tags.Count - 1; i >= 0; --i) + for (var i = _tags.Count - 1; i >= 0; --i) { - if (i >= Tags.Count) + if (i >= _tags.Count) { continue; } - var tag = Tags[i]; + var tag = _tags[i]; if (tag.Name == name) { - Tags.RemoveAt(i); + _tags.RemoveAt(i); + ((ISerializable)this).MarkDirty(); } } } @@ -737,13 +688,14 @@ namespace Server.Accounting /// Tag value. public void SetTag(string name, string value) { - for (var i = 0; i < Tags.Count; ++i) + for (var i = 0; i < _tags.Count; ++i) { - var tag = Tags[i]; + var tag = _tags[i]; if (tag.Name == name) { tag.Value = value; + ((ISerializable)this).MarkDirty(); return; } } @@ -757,9 +709,9 @@ namespace Server.Accounting /// Name of the desired tag value. public string GetTag(string name) { - for (var i = 0; i < Tags.Count; ++i) + for (var i = 0; i < _tags.Count; ++i) { - var tag = Tags[i]; + var tag = _tags[i]; if (tag.Name == name) { @@ -850,7 +802,7 @@ namespace Server.Accounting private static void EventSink_Disconnected(Mobile m) { - if (!(m.Account is Account acc)) + if (m.Account is not Account acc) { return; } @@ -861,22 +813,22 @@ namespace Server.Accounting acc.m_YoungTimer = null; } - if (!(m is PlayerMobile pm)) + if (m is not PlayerMobile pm) { return; } - acc.m_TotalGameTime += Core.Now - pm.SessionStart; + acc.TotalGameTime += Core.Now - pm.SessionStart; } private static void EventSink_Login(Mobile m) { - if (!(m is PlayerMobile pm)) + if (m is not PlayerMobile pm) { return; } - if (!(m.Account is Account acc)) + if (m.Account is not Account acc) { return; } @@ -898,9 +850,9 @@ namespace Server.Accounting { Young = false; - for (var i = 0; i < m_Mobiles.Length; i++) + for (var i = 0; i < _mobiles.Length; i++) { - if (m_Mobiles[i] is PlayerMobile m && m.Young) + if (_mobiles[i] is PlayerMobile { Young: true } m) { m.Young = false; @@ -930,12 +882,12 @@ namespace Server.Accounting private bool UpgradePassword(string password, PasswordProtectionAlgorithm algorithm) { - if (password == null || algorithm < m_PasswordAlgorithm) + if (password == null || algorithm < _passwordAlgorithm) { return false; } - m_PasswordAlgorithm = algorithm; + PasswordAlgorithm = algorithm; Password = password.ReplaceOrdinal("-", string.Empty); return true; } @@ -1136,7 +1088,7 @@ namespace Server.Accounting { var hasAccess = false; - if (m_AccessLevel >= level) + if (_accessLevel >= level) { hasAccess = true; } @@ -1153,7 +1105,7 @@ namespace Server.Accounting } } - Console.WriteLine("{0} {1}", hasAccess ? "yes" : "no", m_AccessLevel); + Console.WriteLine("{0} {1}", hasAccess ? "yes" : "no", _accessLevel); if (!hasAccess) { @@ -1161,11 +1113,11 @@ namespace Server.Accounting } } - var accessAllowed = IPRestrictions.Length == 0 || IPLimiter.IsExempt(ipAddress); + var accessAllowed = _ipRestrictions.Length == 0 || IPLimiter.IsExempt(ipAddress); - for (var i = 0; !accessAllowed && i < IPRestrictions.Length; ++i) + for (var i = 0; !accessAllowed && i < _ipRestrictions.Length; ++i) { - accessAllowed = IPAddress.Parse(IPRestrictions[i]).Equals(ipAddress); + accessAllowed = IPAddress.Parse(_ipRestrictions[i]).Equals(ipAddress); } return accessAllowed; @@ -1190,7 +1142,7 @@ namespace Server.Accounting return; } - if (LoginIPs.Length == 0) + if (_loginIPs.Length == 0) { AccountHandler.IPTable.TryGetValue(ipAddress, out var result); AccountHandler.IPTable[ipAddress] = result + 1; @@ -1198,9 +1150,9 @@ namespace Server.Accounting var contains = false; - for (var i = 0; !contains && i < LoginIPs.Length; ++i) + for (var i = 0; !contains && i < _loginIPs.Length; ++i) { - contains = LoginIPs[i].Equals(ipAddress); + contains = _loginIPs[i].Equals(ipAddress); } if (contains) @@ -1208,7 +1160,7 @@ namespace Server.Accounting return; } - var old = LoginIPs; + var old = _loginIPs; LoginIPs = new IPAddress[old.Length + 1]; for (var i = 0; i < old.Length; ++i) @@ -1239,135 +1191,7 @@ namespace Server.Accounting return hasAccess; } - /// - /// Serializes this Account instance to an XmlTextWriter. - /// - /// The XmlTextWriter instance from which to serialize. - public void Save(XmlTextWriter xml) - { - xml.WriteStartElement("account"); - - xml.WriteStartElement("username"); - xml.WriteString(Username); - xml.WriteEndElement(); - - xml.WriteStartElement("passwordAlgorithm"); - xml.WriteString(m_PasswordAlgorithm.ToString()); - xml.WriteEndElement(); - - xml.WriteStartElement("password"); - xml.WriteString(Password); - xml.WriteEndElement(); - - if (m_AccessLevel != AccessLevel.Player) - { - xml.WriteStartElement("accessLevel"); - xml.WriteString(m_AccessLevel.ToString()); - xml.WriteEndElement(); - } - - if (Flags != 0) - { - xml.WriteStartElement("flags"); - xml.WriteString(XmlConvert.ToString(Flags)); - xml.WriteEndElement(); - } - - xml.WriteStartElement("created"); - xml.WriteString(XmlConvert.ToString(Created, XmlDateTimeSerializationMode.Utc)); - xml.WriteEndElement(); - - xml.WriteStartElement("lastLogin"); - xml.WriteString(XmlConvert.ToString(LastLogin, XmlDateTimeSerializationMode.Utc)); - xml.WriteEndElement(); - - xml.WriteStartElement("totalGameTime"); - xml.WriteString(XmlConvert.ToString(TotalGameTime)); - xml.WriteEndElement(); - - xml.WriteStartElement("chars"); - - for (var i = 0; i < m_Mobiles.Length; ++i) - { - var m = m_Mobiles[i]; - - if (m?.Deleted == false) - { - xml.WriteStartElement("char"); - xml.WriteAttributeString("index", i.ToString()); - xml.WriteString(m.Serial.Value.ToString()); - xml.WriteEndElement(); - } - } - - xml.WriteEndElement(); - - if (m_Comments?.Count > 0) - { - xml.WriteStartElement("comments"); - - for (var i = 0; i < m_Comments.Count; ++i) - { - m_Comments[i].Save(xml); - } - - xml.WriteEndElement(); - } - - if (m_Tags?.Count > 0) - { - xml.WriteStartElement("tags"); - - for (var i = 0; i < m_Tags.Count; ++i) - { - m_Tags[i].Save(xml); - } - - xml.WriteEndElement(); - } - - if (LoginIPs.Length > 0) - { - xml.WriteStartElement("addressList"); - - xml.WriteAttributeString("count", LoginIPs.Length.ToString()); - - for (var i = 0; i < LoginIPs.Length; ++i) - { - xml.WriteStartElement("ip"); - xml.WriteString(LoginIPs[i].ToString()); - xml.WriteEndElement(); - } - - xml.WriteEndElement(); - } - - if (IPRestrictions.Length > 0) - { - xml.WriteStartElement("accessCheck"); - - for (var i = 0; i < IPRestrictions.Length; ++i) - { - xml.WriteStartElement("ip"); - xml.WriteString(IPRestrictions[i]); - xml.WriteEndElement(); - } - - xml.WriteEndElement(); - } - - xml.WriteStartElement("totalGold"); - xml.WriteString(XmlConvert.ToString(TotalGold)); - xml.WriteEndElement(); - - xml.WriteStartElement("totalPlat"); - xml.WriteString(XmlConvert.ToString(TotalPlat)); - xml.WriteEndElement(); - - xml.WriteEndElement(); - } - - public override string ToString() => Username; + public override string ToString() => _username; private class YoungTimer : Timer { diff --git a/Projects/UOContent/Accounting/AccountComment.cs b/Projects/UOContent/Accounting/AccountComment.cs index b1066439f..4260e871f 100644 --- a/Projects/UOContent/Accounting/AccountComment.cs +++ b/Projects/UOContent/Accounting/AccountComment.cs @@ -64,23 +64,6 @@ namespace Server.Accounting /// public DateTime LastModified { get; private set; } - /// - /// Serializes this AccountComment instance to an XmlTextWriter. - /// - /// The XmlTextWriter instance from which to serialize. - public void Save(XmlTextWriter xml) - { - xml.WriteStartElement("comment"); - - xml.WriteAttributeString("addedBy", AddedBy); - - xml.WriteAttributeString("lastModified", XmlConvert.ToString(LastModified, XmlDateTimeSerializationMode.Utc)); - - xml.WriteString(m_Content); - - xml.WriteEndElement(); - } - /// /// Serializes this AccountComment instance. /// diff --git a/Projects/UOContent/Accounting/AccountTag.cs b/Projects/UOContent/Accounting/AccountTag.cs index b7d5379cb..bf3f6ce04 100644 --- a/Projects/UOContent/Accounting/AccountTag.cs +++ b/Projects/UOContent/Accounting/AccountTag.cs @@ -45,18 +45,6 @@ namespace Server.Accounting /// public string Value { get; set; } - /// - /// Serializes this AccountTag instance to an XmlTextWriter. - /// - /// The XmlTextWriter instance from which to serialize. - public void Save(XmlTextWriter xml) - { - xml.WriteStartElement("tag"); - xml.WriteAttributeString("name", Name); - xml.WriteString(Value); - xml.WriteEndElement(); - } - /// /// Serializes this AccountTag instance to an XmlTextWriter. /// diff --git a/Projects/UOContent/Accounting/Accounts.cs b/Projects/UOContent/Accounting/Accounts.cs index 290c9c5cc..ce25d8f46 100644 --- a/Projects/UOContent/Accounting/Accounts.cs +++ b/Projects/UOContent/Accounting/Accounts.cs @@ -10,8 +10,8 @@ namespace Server.Accounting { private static readonly ILogger logger = LogFactory.GetLogger(typeof(Accounts)); - private static readonly Dictionary _accountsByName = new(32, StringComparer.OrdinalIgnoreCase); - private static Dictionary _accountsById = new(32); + private static readonly Dictionary _accountsByName = new(32, StringComparer.OrdinalIgnoreCase); + private static Dictionary _accountsById = new(32); private static Serial _lastAccount; internal static List Types { get; } = new(); @@ -44,7 +44,7 @@ namespace Server.Accounting Persistence.Register("Accounts", Serialize, WriteSnapshot, Deserialize); internal static void Serialize() => - EntityPersistence.SaveEntities(_accountsById.Values, account => account.Serialize()); + EntityPersistence.SaveEntities(_accountsById.Values, account => ((ISerializable)account).Serialize()); internal static void WriteSnapshot(string basePath) { @@ -54,19 +54,19 @@ namespace Server.Accounting public static IEnumerable GetAccounts() => _accountsByName.Values; - public static IAccount GetAccount(string username) + public static Account GetAccount(string username) { _accountsByName.TryGetValue(username, out var a); return a; } - public static void Add(IAccount a) + public static void Add(Account a) { _accountsByName[a.Username] = a; _accountsById[a.Serial] = a; } - public static void Remove(IAccount a) + public static void Remove(Account a) { _accountsByName.Remove(a.Username); _accountsById.Remove(a.Serial); @@ -85,7 +85,7 @@ namespace Server.Accounting IIndexInfo indexInfo = new EntityTypeIndex("Accounts"); - _accountsById = EntityPersistence.LoadIndex(path, indexInfo, out List> accounts); + _accountsById = EntityPersistence.LoadIndex(path, indexInfo, out List> accounts); EntityPersistence.LoadData(path, indexInfo, accounts); foreach (var a in _accountsById.Values) diff --git a/Projects/UOContent/Gumps/AdminGump.cs b/Projects/UOContent/Gumps/AdminGump.cs index 4f1b2eb2f..50bbd70a2 100644 --- a/Projects/UOContent/Gumps/AdminGump.cs +++ b/Projects/UOContent/Gumps/AdminGump.cs @@ -1056,7 +1056,7 @@ namespace Server.Gumps if (m_List == null) { - ipRestrictions = a.IPRestrictions.ToList(); + ipRestrictions = a.IpRestrictions.ToList(); m_List = ipRestrictions.ToList(); } else @@ -3000,7 +3000,7 @@ namespace Server.Gumps } else { - var list = a.IPRestrictions; + var list = a.IpRestrictions; var contains = false; for (var i = 0; !contains && i < list.Length; ++i) @@ -3023,7 +3023,7 @@ namespace Server.Gumps newList[list.Length] = ip; - a.IPRestrictions = newList; + a.IpRestrictions = newList; notice = $"{ip} : Added to restriction list."; } @@ -3888,9 +3888,9 @@ namespace Server.Gumps } else if (m_PageType == AdminGumpPage.AccountDetails_Access_Restrictions) { - var list = a.IPRestrictions.ToList(); + var list = a.IpRestrictions.ToList(); list.Remove(m_List[index] as string); - a.IPRestrictions = list.ToArray(); + a.IpRestrictions = list.ToArray(); from.SendGump( new AdminGump( diff --git a/Projects/UOContent/Migrations/Server.Accounting.Account.v2.json b/Projects/UOContent/Migrations/Server.Accounting.Account.v2.json new file mode 100644 index 000000000..80f2af2a0 --- /dev/null +++ b/Projects/UOContent/Migrations/Server.Accounting.Account.v2.json @@ -0,0 +1,108 @@ +{ + "version": 2, + "type": "Server.Accounting.Account", + "properties": [ + { + "name": "Username", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [] + }, + { + "name": "PasswordAlgorithm", + "type": "Server.Accounting.Security.PasswordProtectionAlgorithm", + "rule": "EnumMigrationRule", + "ruleArguments": [] + }, + { + "name": "Password", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [] + }, + { + "name": "AccessLevel", + "type": "Server.AccessLevel", + "rule": "EnumMigrationRule", + "ruleArguments": [] + }, + { + "name": "Flags", + "type": "int", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [] + }, + { + "name": "Created", + "type": "System.DateTime", + "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": "LoginIPs", + "type": "System.Net.IPAddress[]", + "rule": "ArrayMigrationRule", + "ruleArguments": [ + "System.Net.IPAddress", + "PrimitiveTypeMigrationRule" + ] + }, + { + "name": "IpRestrictions", + "type": "string[]", + "rule": "ArrayMigrationRule", + "ruleArguments": [ + "string", + "PrimitiveTypeMigrationRule" + ] + }, + { + "name": "TotalGameTime", + "type": "System.TimeSpan", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [] + }, + { + "name": "Email", + "type": "string", + "rule": "PrimitiveTypeMigrationRule", + "ruleArguments": [] + } + ] +} \ No newline at end of file