using System.Buffers.Binary; using AcDream.Core.Net.Messages; namespace AcDream.Core.Net.Tests.Messages; public sealed class ClientCommandRequestsTests { public static TheoryData, uint> ParameterlessActions => new() { { ClientCommandRequests.BuildMarketplace, ClientCommandRequests.MarketplaceOpcode }, { ClientCommandRequests.BuildPkArena, ClientCommandRequests.PkArenaOpcode }, { ClientCommandRequests.BuildPkLiteArena, ClientCommandRequests.PkLiteArenaOpcode }, { ClientCommandRequests.BuildHouseRecall, ClientCommandRequests.HouseRecallOpcode }, { ClientCommandRequests.BuildMansionRecall, ClientCommandRequests.MansionRecallOpcode }, { ClientCommandRequests.BuildSuicide, ClientCommandRequests.SuicideOpcode }, { ClientCommandRequests.BuildClearFriends, ClientCommandRequests.ClearFriendsOpcode }, { ClientCommandRequests.BuildClearConsent, ClientCommandRequests.ClearConsentOpcode }, { ClientCommandRequests.BuildDisplayConsent, ClientCommandRequests.DisplayConsentOpcode }, }; [Theory] [MemberData(nameof(ParameterlessActions))] public void ParameterlessAction_MatchesRetailEnvelope( Func build, uint opcode) { byte[] body = build(0x1234u); Assert.Equal(12, body.Length); Assert.Equal(InteractRequests.GameActionEnvelope, Read(body, 0)); Assert.Equal(0x1234u, Read(body, 4)); Assert.Equal(opcode, Read(body, 8)); } [Theory] [InlineData(false, ClientCommandRequests.QueryAgeOpcode)] [InlineData(true, ClientCommandRequests.QueryBirthOpcode)] public void CharacterQuery_MatchesRetailPayload(bool birth, uint opcode) { byte[] body = birth ? ClientCommandRequests.BuildQueryBirth(7u, 0x50000001u) : ClientCommandRequests.BuildQueryAge(7u, 0x50000001u); Assert.Equal(16, body.Length); Assert.Equal(InteractRequests.GameActionEnvelope, Read(body, 0)); Assert.Equal(7u, Read(body, 4)); Assert.Equal(opcode, Read(body, 8)); Assert.Equal(0x50000001u, Read(body, 12)); } [Fact] public void ConfirmationResponse_MatchesRetailPayload() { byte[] body = ClientCommandRequests.BuildConfirmationResponse( 9u, confirmationType: 7u, contextId: 42u, accepted: true); Assert.Equal(24, body.Length); Assert.Equal(InteractRequests.GameActionEnvelope, Read(body, 0)); Assert.Equal(9u, Read(body, 4)); Assert.Equal(ClientCommandRequests.ConfirmationResponseOpcode, Read(body, 8)); Assert.Equal(7u, Read(body, 12)); Assert.Equal(42u, Read(body, 16)); Assert.Equal(1u, Read(body, 20)); } public static TheoryData, uint> StringActions => new() { { ClientCommandRequests.BuildSetAfkMessage, ClientCommandRequests.SetAfkMessageOpcode }, { ClientCommandRequests.BuildEmote, ClientCommandRequests.EmoteOpcode }, { ClientCommandRequests.BuildAddFriend, ClientCommandRequests.AddFriendOpcode }, { ClientCommandRequests.BuildRemoveConsent, ClientCommandRequests.RemoveConsentOpcode }, }; [Theory] [MemberData(nameof(StringActions))] public void StringAction_PacksAlignedCp1252String16L( Func build, uint opcode) { byte[] body = build(11u, "Bjørn"); Assert.Equal(20, body.Length); Assert.Equal(InteractRequests.GameActionEnvelope, Read(body, 0)); Assert.Equal(11u, Read(body, 4)); Assert.Equal(opcode, Read(body, 8)); Assert.Equal(5, BinaryPrimitives.ReadUInt16LittleEndian(body.AsSpan(12))); Assert.Equal([0x42, 0x6A, 0xF8, 0x72, 0x6E], body[14..19]); Assert.Equal(0, body[19]); } [Fact] public void SetAfkMode_MatchesRetailBooleanPayload() { byte[] body = ClientCommandRequests.BuildSetAfkMode(12u, away: true); Assert.Equal(16, body.Length); Assert.Equal(ClientCommandRequests.SetAfkModeOpcode, Read(body, 8)); Assert.Equal(1u, Read(body, 12)); } [Fact] public void FriendAndGlobalSquelchIntegerActions_MatchRetailPayloads() { byte[] remove = ClientCommandRequests.BuildRemoveFriend(2u, 0x50000001u); byte[] filter = ClientCommandRequests.BuildModifyGlobalSquelch(3u, add: false, 17u); Assert.Equal(ClientCommandRequests.RemoveFriendOpcode, Read(remove, 8)); Assert.Equal(0x50000001u, Read(remove, 12)); Assert.Equal(ClientCommandRequests.ModifyGlobalSquelchOpcode, Read(filter, 8)); Assert.Equal(0u, Read(filter, 12)); Assert.Equal(17u, Read(filter, 16)); } [Fact] public void CharacterSquelch_PreservesRetailFieldOrder() { byte[] body = ClientCommandRequests.BuildModifyCharacterSquelch( 4u, add: true, 0x50000001u, "Alice", 12u); Assert.Equal(32, body.Length); Assert.Equal(ClientCommandRequests.ModifyCharacterSquelchOpcode, Read(body, 8)); Assert.Equal(1u, Read(body, 12)); Assert.Equal(0x50000001u, Read(body, 16)); Assert.Equal(5, BinaryPrimitives.ReadUInt16LittleEndian(body.AsSpan(20))); Assert.Equal(12u, Read(body, 28)); } [Fact] public void AccountSquelch_PreservesRetailFieldOrder() { byte[] body = ClientCommandRequests.BuildModifyAccountSquelch( 5u, add: false, "Alice"); Assert.Equal(24, body.Length); Assert.Equal(ClientCommandRequests.ModifyAccountSquelchOpcode, Read(body, 8)); Assert.Equal(0u, Read(body, 12)); Assert.Equal(5, BinaryPrimitives.ReadUInt16LittleEndian(body.AsSpan(16))); } [Fact] public void DesiredComponentLevel_MatchesRetailTwoIntegerPayload() { byte[] body = ClientCommandRequests.BuildSetDesiredComponentLevel( 6u, 0x12000042u, 25u); Assert.Equal(20, body.Length); Assert.Equal(ClientCommandRequests.SetDesiredComponentLevelOpcode, Read(body, 8)); Assert.Equal(0x12000042u, Read(body, 12)); Assert.Equal(25u, Read(body, 16)); } [Fact] public void LegacyFriendsCommand_IsAControlMessageWithoutGameActionEnvelope() { byte[] body = ClientCommandRequests.BuildLegacyFriendsCommand(0u, string.Empty); Assert.Equal(12, body.Length); Assert.Equal(ClientCommandRequests.LegacyFriendsOpcode, Read(body, 0)); Assert.Equal(0u, Read(body, 4)); Assert.Equal(0, BinaryPrimitives.ReadUInt16LittleEndian(body.AsSpan(8))); } private static uint Read(byte[] body, int offset) => BinaryPrimitives.ReadUInt32LittleEndian(body.AsSpan(offset)); }