using System; using System.Buffers; using System.Collections.Generic; using System.IO.Pipelines; using System.Linq; using System.Net; using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Http.Features; using Server.Accounting; using Server.Network; using Xunit; namespace Server.Tests.Network.Packets { public class AccountPacketTests : IClassFixture { [Fact] public void TestChangeCharacter() { var firstMobile = new Mobile(0x1); firstMobile.DefaultMobileInit(); firstMobile.Name = "Test Mobile"; var account = new TestAccount(new[] { firstMobile, null, null }); var data = new ChangeCharacter(account).Compile(); Span expectedData = stackalloc byte[5 + account.Length * 60]; var pos = 0; expectedData.Write(ref pos, (byte)0x81); // Packet ID expectedData.Write(ref pos, (ushort)expectedData.Length); // Length expectedData.Write(ref pos, (byte)1); // Count of non-null characters expectedData.Write(ref pos, (byte)0); for (var i = 0; i < account.Length; ++i) { var m = account[i]; if (m == null) { #if NO_LOCAL_INIT expectedData.Clear(ref pos, 60); #else pos += 60; #endif } else { expectedData.WriteAsciiFixed(ref pos, m.Name, 30); #if NO_LOCAL_INIT expectedData.Clear(ref pos, 30); // Password (empty) #else pos += 30; #endif } } AssertThat.Equal(data, expectedData); } [Fact] public void TestClientVersionReq() { var data = new ClientVersionReq().Compile(); Span expectedData = stackalloc byte[] { 0xBD, // Packet ID 0x00, 0x03 // Length }; AssertThat.Equal(data, expectedData); } [Fact] public void TestDeleteResult() { var data = new DeleteResult(DeleteResultType.BadRequest).Compile(); Span expectedData = stackalloc byte[2]; var pos = 0; expectedData.Write(ref pos, (byte)0x85); // Packet ID expectedData.Write(ref pos, (byte)DeleteResultType.BadRequest); AssertThat.Equal(data, expectedData); } [Fact] public void TestPopupMessage() { var data = new PopupMessage(PMMessage.IdleWarning).Compile(); Span expectedData = stackalloc byte[2]; var pos = 0; expectedData.Write(ref pos, (byte)0x53); // Packet ID expectedData.Write(ref pos, (byte)PMMessage.IdleWarning); AssertThat.Equal(data, expectedData); } [Theory] [InlineData(ProtocolChanges.Version70610)] [InlineData(ProtocolChanges.Version6000)] public void TestSupportedFeatures(ProtocolChanges protocolChanges) { var firstMobile = new Mobile(0x1); firstMobile.DefaultMobileInit(); firstMobile.Name = "Test Mobile"; var account = new TestAccount(new[] { firstMobile, null, null, null, null }); var ns = new NetState( new TestConnectionContext { RemoteEndPoint = IPEndPoint.Parse("127.0.0.1") } ) { Account = account, ProtocolChanges = protocolChanges }; var data = new SupportedFeatures(ns).Compile(); Span expectedData = stackalloc byte[ns.ExtendedSupportedFeatures ? 5 : 3]; var pos = 0; expectedData[pos++] = 0xB9; // Packet ID var flags = ExpansionInfo.GetFeatures(Expansion.EJ); if (ns.Account.Limit >= 6) { flags |= FeatureFlags.LiveAccount; flags &= ~FeatureFlags.UOTD; if (ns.Account.Limit > 6) flags |= FeatureFlags.SeventhCharacterSlot; else flags |= FeatureFlags.SixthCharacterSlot; } if (ns.ExtendedSupportedFeatures) expectedData.Write(ref pos, (uint)flags); else expectedData.Write(ref pos, (ushort)flags); AssertThat.Equal(data, expectedData); } [Fact] public void TestLoginConfirm() { var m = new Mobile(0x1); m.DefaultMobileInit(); m.Body = 0x100; m.X = 100; m.Y = 10; m.Z = -10; m.Direction = Direction.Down; m.LogoutMap = Map.Felucca; var data = new LoginConfirm(m).Compile(); Span expectedData = stackalloc byte[37]; var pos = 0; expectedData.Write(ref pos, (byte)0x1B); // Packet ID expectedData.Write(ref pos, m.Serial); #if NO_LOCAL_INIT expectedData.Write(ref pos, 0); #else pos += 4; #endif expectedData.Write(ref pos, (ushort)m.Body); expectedData.Write(ref pos, (ushort)m.X); expectedData.Write(ref pos, (ushort)m.Y); expectedData.Write(ref pos, (short)m.Z); expectedData.Write(ref pos, (byte)m.Direction); #if NO_LOCAL_INIT expectedData.Write(ref pos, (byte)0); #else pos++; #endif expectedData.Write(ref pos, 0xFFFFFFFF); #if NO_LOCAL_INIT expectedData.Write(ref pos, 0); #else pos += 4; #endif var map = m.Map; if (map == null || map == Map.Internal) map = m.LogoutMap; expectedData.Write(ref pos, (ushort)(map?.Width ?? Map.Felucca.Width)); expectedData.Write(ref pos, (ushort)(map?.Height ?? Map.Felucca.Height)); AssertThat.Equal(data, expectedData); } [Fact] public void TestLoginComplete() { var data = new LoginComplete().Compile(); Span expectedData = stackalloc byte[] { 0x55 // Packet ID }; AssertThat.Equal(data, expectedData); } [Fact] public void TestCharacterListUpdate() { var firstMobile = new Mobile(0x1); firstMobile.DefaultMobileInit(); firstMobile.Name = "Test Mobile"; var account = new TestAccount(new[] { firstMobile, null, null, null, null }); var data = new CharacterListUpdate(account).Compile(); Span expectedData = stackalloc byte[4 + account.Length * 60]; var pos = 0; expectedData.Write(ref pos, (byte)0x86); // Packet ID expectedData.Write(ref pos, (ushort)expectedData.Length); // Length var highSlot = -1; for (var i = account.Length - 1; i >= 0; i--) if (account[i] != null) { highSlot = i; break; } var count = Math.Max(Math.Max(highSlot + 1, account.Limit), 5); expectedData.Write(ref pos, (byte)count); for (var i = 0; i < count; i++) { var m = account[i]; if (m != null) { expectedData.WriteAsciiFixed(ref pos, m.Name, 30); #if NO_LOCAL_INIT expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, 0); expectedData.Write(ref pos, (ushort)0); #else pos += 30; #endif } else { #if NO_LOCAL_INIT expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, 0); #else pos += 60; #endif } } AssertThat.Equal(data, expectedData); } [Fact] public void TestCharacterList() { var firstMobile = new Mobile(0x1); firstMobile.DefaultMobileInit(); firstMobile.Name = "Test Mobile"; var account = new TestAccount(new[] { firstMobile, null, null, null, null }); var info = new[] { new CityInfo("Test City", "Test Building", 50, 100, 10, -10) }; var data = new CharacterList(account, info).Compile(); Span expectedData = stackalloc byte[11 + account.Length * 60 + info.Length * 89]; var pos = 0; expectedData.Write(ref pos, (byte)0xA9); // Packet ID expectedData.Write(ref pos, (ushort)expectedData.Length); // Length var highSlot = -1; for (var i = account.Length - 1; i >= 0; i--) if (account[i] != null) { highSlot = i; break; } var count = Math.Max(Math.Max(highSlot + 1, account.Limit), 5); expectedData.Write(ref pos, (byte)count); for (var i = 0; i < count; i++) { var m = account[i]; if (m != null) { expectedData.WriteAsciiFixed(ref pos, m.Name, 30); #if NO_LOCAL_INIT expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, 0); expectedData.Write(ref pos, (ushort)0); #else pos += 30; #endif } else { #if NO_LOCAL_INIT expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, 0); #else pos += 60; #endif } } expectedData.Write(ref pos, (byte)info.Length); for (var i = 0; i < info.Length; i++) { var ci = info[i]; expectedData.Write(ref pos, (byte)i); expectedData.WriteAsciiFixed(ref pos, ci.City, 32); expectedData.WriteAsciiFixed(ref pos, ci.Building, 32); expectedData.Write(ref pos, ci.X); expectedData.Write(ref pos, ci.Y); expectedData.Write(ref pos, ci.Z); expectedData.Write(ref pos, ci.Map.MapID); expectedData.Write(ref pos, ci.Description); #if NO_LOCAL_INIT expectedData.Write(ref pos, 0); #else pos += 4; #endif } var flags = ExpansionInfo.GetInfo(Expansion.EJ).CharacterListFlags; if (count > 6) flags |= CharacterListFlags.SeventhCharacterSlot | CharacterListFlags.SixthCharacterSlot; // 7th Character Slot else if (count == 6) flags |= CharacterListFlags.SixthCharacterSlot; // 6th Character Slot else if (account.Limit == 1) flags |= CharacterListFlags.SlotLimit & CharacterListFlags.OneCharacterSlot; // Limit Characters & One Character expectedData.Write(ref pos, (int)flags); expectedData.Write(ref pos, (short)-1); AssertThat.Equal(data, expectedData); } [Fact] public void TestCharacterListOld() { var firstMobile = new Mobile(0x1); firstMobile.DefaultMobileInit(); firstMobile.Name = "Test Mobile"; var account = new TestAccount(new[] { firstMobile, null, null, null, null }); var info = new[] { new CityInfo("Test City", "Test Building", 50, 100, 10, -10) }; var data = new CharacterListOld(account, info).Compile(); Span expectedData = stackalloc byte[9 + account.Length * 60 + info.Length * 63]; var pos = 0; expectedData.Write(ref pos, (byte)0xA9); // Packet ID expectedData.Write(ref pos, (ushort)expectedData.Length); // Length var highSlot = -1; for (var i = account.Length - 1; i >= 0; i--) if (account[i] != null) { highSlot = i; break; } var count = Math.Max(Math.Max(highSlot + 1, account.Limit), 5); expectedData.Write(ref pos, (byte)count); for (var i = 0; i < count; i++) { var m = account[i]; if (m != null) { expectedData.WriteAsciiFixed(ref pos, m.Name, 30); #if NO_LOCAL_INIT expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, 0); expectedData.Write(ref pos, (ushort)0); #else pos += 30; #endif } else { #if NO_LOCAL_INIT expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, (ulong)0); expectedData.Write(ref pos, 0); #else pos += 60; #endif } } expectedData.Write(ref pos, (byte)info.Length); for (var i = 0; i < info.Length; i++) { var ci = info[i]; expectedData.Write(ref pos, (byte)i); expectedData.WriteAsciiFixed(ref pos, ci.City, 31); expectedData.WriteAsciiFixed(ref pos, ci.Building, 31); } var flags = ExpansionInfo.GetInfo(Expansion.EJ).CharacterListFlags; if (count > 6) flags |= CharacterListFlags.SeventhCharacterSlot | CharacterListFlags.SixthCharacterSlot; // 7th Character Slot else if (count == 6) flags |= CharacterListFlags.SixthCharacterSlot; // 6th Character Slot else if (account.Limit == 1) flags |= CharacterListFlags.SlotLimit & CharacterListFlags.OneCharacterSlot; // Limit Characters & One Character expectedData.Write(ref pos, (int)flags); AssertThat.Equal(data, expectedData); } [Fact] public void TestAccountLoginRej() { var reason = ALRReason.BadComm; var data = new AccountLoginRej(reason).Compile(); Span expectedData = stackalloc byte[2]; var pos = 0; expectedData.Write(ref pos, (byte)0x82); // Packet ID expectedData.Write(ref pos, (byte)reason); AssertThat.Equal(data, expectedData); } [Fact] public void TestAccountLoginAck() { var info = new[] { new ServerInfo("Test Server", 0, TimeZoneInfo.Local, IPEndPoint.Parse("127.0.0.1")) }; var data = new AccountLoginAck(info).Compile(); Span expectedData = stackalloc byte[6 + info.Length * 40]; var pos = 0; expectedData.Write(ref pos, (byte)0xA8); // Packet ID expectedData.Write(ref pos, (ushort)expectedData.Length); expectedData.Write(ref pos, (byte)0x5D); // Unknown expectedData.Write(ref pos, (ushort)info.Length); for (var i = 0; i < info.Length; i++) { var si = info[i]; expectedData.Write(ref pos, (ushort)i); expectedData.WriteAsciiFixed(ref pos, si.Name, 32); expectedData.Write(ref pos, (byte)si.FullPercent); expectedData.Write(ref pos, (byte)si.TimeZone); expectedData.Write(ref pos, Utility.GetAddressValue(si.Address.Address)); } AssertThat.Equal(data, expectedData); } [Fact] public void TestPlayServerAck() { var si = new ServerInfo("Test Server", 0, TimeZoneInfo.Local, IPEndPoint.Parse("127.0.0.1")); var data = new PlayServerAck(si).Compile(); var addr = Utility.GetAddressValue(si.Address.Address); Span expectedData = stackalloc byte[11]; var pos = 0; expectedData.Write(ref pos, (byte)0x8C); // Packet ID expectedData.WriteLE(ref pos, addr); expectedData.Write(ref pos, (ushort)si.Address.Port); expectedData.Write(ref pos, -1); // Auth ID AssertThat.Equal(data, expectedData); } internal class TestAccount : IAccount, IComparable { private readonly Mobile[] m_Mobiles; private string m_Password; public TestAccount(Mobile[] mobiles) { m_Mobiles = mobiles; foreach (var mobile in mobiles) if (mobile != null) mobile.Account = this; Length = mobiles.Length; Count = mobiles.Count(t => t != null); Limit = mobiles.Length; } public int TotalGold { get; private set; } public int TotalPlat { get; private set; } public bool DepositGold(int amount) { TotalGold += amount; return true; } public bool DepositPlat(int amount) { TotalPlat += amount; return true; } public bool WithdrawGold(int amount) { if (TotalGold - amount < 0) return false; TotalGold -= amount; return true; } public bool WithdrawPlat(int amount) { if (TotalPlat - amount < 0) return false; TotalPlat -= amount; return true; } public long GetTotalGold() => TotalGold + TotalPlat * 100; public int CompareTo(IAccount other) => other == null ? 1 : Username.CompareTo(other.Username); public string Username { get; set; } public string Email { get; set; } public AccessLevel AccessLevel { get; set; } public int Length { get; } public int Limit { get; } public int Count { get; } public Mobile this[int index] { get => m_Mobiles[index]; set => m_Mobiles[index] = value; } public void Delete() { } public void SetPassword(string password) { m_Password = password; } public bool CheckPassword(string password) => m_Password == password; public int CompareTo(TestAccount other) => other == null ? 1 : Username.CompareTo(other.Username); } internal class TestConnectionContext : ConnectionContext { public override string ConnectionId { get; set; } public override IFeatureCollection Features { get; } public override IDictionary Items { get; set; } public override IDuplexPipe Transport { get; set; } } } }