diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 61979bc8..3845c4a3 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -447,7 +447,7 @@ object GamePacketOpcode extends Enumeration { case 0x6c => game.LootItemMessage.decode case 0x6d => game.VehicleSubStateMessage.decode case 0x6e => game.SquadMembershipRequest.decode - case 0x6f => noDecoder(SquadMembershipResponse) + case 0x6f => game.SquadMembershipResponse.decode // OPCODES 0x70-7f case 0x70 => noDecoder(SquadMemberEvent) diff --git a/common/src/main/scala/net/psforever/packet/game/SquadMembershipRequest.scala b/common/src/main/scala/net/psforever/packet/game/SquadMembershipRequest.scala index ee55360d..55d73197 100644 --- a/common/src/main/scala/net/psforever/packet/game/SquadMembershipRequest.scala +++ b/common/src/main/scala/net/psforever/packet/game/SquadMembershipRequest.scala @@ -22,7 +22,7 @@ final case class SquadMembershipRequest(request_type : SquadRequestType.Value, extends PlanetSideGamePacket { request_type match { case SquadRequestType.Accept | SquadRequestType.Reject | SquadRequestType.Disband | - SquadRequestType.PAccept | SquadRequestType.PReject | SquadRequestType.PDisband => + SquadRequestType.PlatoonAccept | SquadRequestType.PlatoonReject | SquadRequestType.PlatoonDisband => assert(unk3.isEmpty, s"a $request_type request requires the unk3 field be undefined") case _ => assert(unk3.nonEmpty, s"a $request_type request requires the unk3 field be defined") @@ -43,9 +43,9 @@ object SquadMembershipRequest extends Marshallable[SquadMembershipRequest] { conditional(request_type != SquadRequestType.Accept && request_type != SquadRequestType.Reject && request_type != SquadRequestType.Disband && - request_type != SquadRequestType.PAccept && - request_type != SquadRequestType.PReject && - request_type != SquadRequestType.PDisband, "unk3" | uint32L) :: + request_type != SquadRequestType.PlatoonAccept && + request_type != SquadRequestType.PlatoonReject && + request_type != SquadRequestType.PlatoonDisband, "unk3" | uint32L) :: (("player_name" | PacketHelpers.encodedWideStringAligned(4)) >>:~ { pname => conditional(request_type == SquadRequestType.Invite, "unk5" | optional(bool, PacketHelpers.encodedWideStringAligned({if(pname.length == 0) 3 else 7})) diff --git a/common/src/main/scala/net/psforever/packet/game/SquadMembershipResponse.scala b/common/src/main/scala/net/psforever/packet/game/SquadMembershipResponse.scala new file mode 100644 index 00000000..0d01a938 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/SquadMembershipResponse.scala @@ -0,0 +1,52 @@ +// Copyright (c) 2019 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import net.psforever.types.SquadRequestType +import scodec.Codec +import scodec.codecs._ + +/** + * na + * @param request_type the type of request being answered + * @param unk1 na + * @param unk2 na + * @param unk3 na + * @param unk4 na + * @param player_name the player being affected, if applicable + * @param unk5 na + * @param unk6 na + */ +final case class SquadMembershipResponse(request_type : SquadRequestType.Value, + unk1 : Int, + unk2 : Int, + unk3 : Long, + unk4 : Option[Long], + player_name : String, + unk5 : Boolean, + unk6 : Option[Option[String]]) + extends PlanetSideGamePacket { + /* + if(response_type != 6 && response_type != 12) + assert(unk5.isDefined, "unk5 field required") + else + assert(!unk5.isDefined, "unk5 defined but unk1 invalid value") + */ + type Packet = SquadMembershipResponse + def opcode = GamePacketOpcode.SquadMembershipResponse + def encode = SquadMembershipResponse.encode(this) +} + +object SquadMembershipResponse extends Marshallable[SquadMembershipResponse] { + implicit val codec : Codec[SquadMembershipResponse] = ( + "request_type" | SquadRequestType.codec >>:~ { d => + ("unk1" | uint(5)) :: + ("unk2" | uint2) :: + ("unk3" | uint32L) :: + ("unk4" | conditional(d != SquadRequestType.Promote && d != SquadRequestType.PlatoonLeave, uint32L)) :: + ("player_name" | PacketHelpers.encodedWideStringAligned(5)) :: + ("unk5" | bool) :: + conditional(d != SquadRequestType.Invite, optional(bool, "unk6" | PacketHelpers.encodedWideStringAligned(6))) + } + ).as[SquadMembershipResponse] +} diff --git a/common/src/main/scala/net/psforever/types/SquadRequestType.scala b/common/src/main/scala/net/psforever/types/SquadRequestType.scala index 6b104c68..8d7e2435 100644 --- a/common/src/main/scala/net/psforever/types/SquadRequestType.scala +++ b/common/src/main/scala/net/psforever/types/SquadRequestType.scala @@ -6,7 +6,8 @@ import scodec.codecs._ object SquadRequestType extends Enumeration { type Type = Value - val Invite, //00 + val + Invite, //00 Unk01, //01 Accept, //02 Reject, //03 @@ -14,12 +15,12 @@ object SquadRequestType extends Enumeration { Leave, //05 Promote, //06 Disband, //07 - PInvite, //08 - PAccept, //09 - PReject, //10 - PCancel, //11 - PLeave, //12 - PDisband, //13 + PlatoonInvite, //08 + PlatoonAccept, //09 + PlatoonReject, //10 + PlatoonCancel, //11 + PlatoonLeave, //12 + PlatoonDisband, //13 Unk14, //14 Unk15 //15 = Value diff --git a/common/src/test/scala/game/SquadMembershipResponseTest.scala b/common/src/test/scala/game/SquadMembershipResponseTest.scala new file mode 100644 index 00000000..c640df04 --- /dev/null +++ b/common/src/test/scala/game/SquadMembershipResponseTest.scala @@ -0,0 +1,421 @@ +// Copyright (c) 2019 PSForever +package game + +import net.psforever.packet._ +import net.psforever.packet.game._ +import net.psforever.types.SquadRequestType +import org.specs2.mutable._ +import scodec.bits._ + +class SquadMembershipResponseTest extends Specification { + val string_01 = hex"6f0 00854518050db2260108048006f006600440000" + val string_02 = hex"6f0 0049e8220112aa1e01100530050004f0049004c0045005200530080" + val string_11 = hex"6f1 995364f2040000000100080" + val string_12 = hex"6f1 90cadcf4040000000100080" + val string_21 = hex"6f2 010db2260085451805140560069007200750073004700690076006500720080" + val string_22 = hex"6f2 010db22601da03aa03140560069007200750073004700690076006500720080" + val string_31 = hex"6f3 07631db202854518050a048004d0046004900430000" + val string_32 = hex"6f3 04c34fb402854518050e0440041004e00310031003100310000" + val string_41 = hex"6f4 04cadcf405bbbef405140530041007200610069007300560061006e00750000" + val string_42 = hex"6f4 05c9c0f405d71aec0516041006900720049006e006a006500630074006f00720000" + val string_51 = hex"6f5 0249e8220049e822010e0430043005200490044004500520080" + val string_71 = hex"6f7 1049e822000000000100080" + val string_72 = hex"6f7 00cadcf4041355ae03100570069007a006b00690064003400350080" + val string_81 = hex"6f8 001355ae02cadcf405100570069007a006b00690064003400350000" + val string_91 = hex"6f9 008310080115aef40500080" + val string_92 = hex"6f9 001355ae02cadcf405100570069007a006b00690064003400350000" + val string_b1 = hex"6fb 021355ae02cadcf405140530041007200610069007300560061006e00750000" + + "SquadMembershipResponse" should { + "decode (0-1)" in { + PacketCoding.DecodePacket(string_01).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Invite + unk2 mustEqual 0 + unk3 mustEqual 0 + unk4 mustEqual 42771010L + unk5.contains(1300870L) mustEqual true + unk6 mustEqual "HofD" + unk7 mustEqual false + unk8.isEmpty mustEqual true + case _ => + ko + } + } + + "decode (0-2)" in { + PacketCoding.DecodePacket(string_02).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Invite + unk2 mustEqual 0 + unk3 mustEqual 0 + unk4 mustEqual 1176612L + unk5.contains(1004937L) mustEqual true + unk6 mustEqual "SPOILERS" + unk7 mustEqual true + unk8.isEmpty mustEqual true + case _ => + ko + } + } + + "decode (1-1)" in { + PacketCoding.DecodePacket(string_11).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Unk01 + unk2 mustEqual 19 + unk3 mustEqual 0 + unk4 mustEqual 41530025L + unk5.contains(0L) mustEqual true + unk6 mustEqual "" + unk7 mustEqual true + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (1-2)" in { + PacketCoding.DecodePacket(string_12).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Unk01 + unk2 mustEqual 18 + unk3 mustEqual 0 + unk4 mustEqual 41578085L + unk5.contains(0L) mustEqual true + unk6 mustEqual "" + unk7 mustEqual true + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (2-1)" in { + PacketCoding.DecodePacket(string_21).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Accept + unk2 mustEqual 0 + unk3 mustEqual 0 + unk4 mustEqual 1300870L + unk5.contains(42771010L) mustEqual true + unk6 mustEqual "VirusGiver" + unk7 mustEqual true + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (2-2)" in { + PacketCoding.DecodePacket(string_22).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Accept + unk2 mustEqual 0 + unk3 mustEqual 0 + unk4 mustEqual 1300870L + unk5.contains(30736877L) mustEqual true + unk6 mustEqual "VirusGiver" + unk7 mustEqual true + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (3-1)" in { + PacketCoding.DecodePacket(string_31).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Reject + unk2 mustEqual 0 + unk3 mustEqual 3 + unk4 mustEqual 31035057L + unk5.contains(42771010L) mustEqual true + unk6 mustEqual "HMFIC" + unk7 mustEqual false + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (3-2)" in { + PacketCoding.DecodePacket(string_32).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Reject + unk2 mustEqual 0 + unk3 mustEqual 2 + unk4 mustEqual 31106913L + unk5.contains(42771010L) mustEqual true + unk6 mustEqual "DAN1111" + unk7 mustEqual false + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (4-1)" in { + PacketCoding.DecodePacket(string_41).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Cancel + unk2 mustEqual 0 + unk3 mustEqual 2 + unk4 mustEqual 41578085L + unk5.contains(41607133L) mustEqual true + unk6 mustEqual "SAraisVanu" + unk7 mustEqual false + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (4-2)" in { + PacketCoding.DecodePacket(string_42).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Cancel + unk2 mustEqual 0 + unk3 mustEqual 2 + unk4 mustEqual 41607396L + unk5.contains(41324011L) mustEqual true + unk6 mustEqual "AirInjector" + unk7 mustEqual false + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (5-1)" in { + PacketCoding.DecodePacket(string_51).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Leave + unk2 mustEqual 0 + unk3 mustEqual 1 + unk4 mustEqual 1176612L + unk5.contains(1176612L) mustEqual true + unk6 mustEqual "CCRIDER" + unk7 mustEqual true + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (7-1)" in { + PacketCoding.DecodePacket(string_71).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Disband + unk2 mustEqual 2 + unk3 mustEqual 0 + unk4 mustEqual 1176612L + unk5.contains(0L) mustEqual true + unk6 mustEqual "" + unk7 mustEqual true + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (7-2)" in { + PacketCoding.DecodePacket(string_72).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.Disband + unk2 mustEqual 0 + unk3 mustEqual 0 + unk4 mustEqual 41578085L + unk5.contains(30910985L) mustEqual true + unk6 mustEqual "Wizkid45" + unk7 mustEqual true + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (8-1)" in { + PacketCoding.DecodePacket(string_81).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.PlatoonInvite + unk2 mustEqual 0 + unk3 mustEqual 0 + unk4 mustEqual 30910985L + unk5.contains(41578085L) mustEqual true + unk6 mustEqual "Wizkid45" + unk7 mustEqual false + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (9-1)" in { + PacketCoding.DecodePacket(string_91).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.PlatoonAccept + unk2 mustEqual 0 + unk3 mustEqual 0 + unk4 mustEqual 297025L + unk5.contains(41605002L) mustEqual true + unk6 mustEqual "" + unk7 mustEqual true + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (9-2)" in { + PacketCoding.DecodePacket(string_92).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.PlatoonAccept + unk2 mustEqual 0 + unk3 mustEqual 0 + unk4 mustEqual 30910985L + unk5.contains(41578085L) mustEqual true + unk6 mustEqual "Wizkid45" + unk7 mustEqual false + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "decode (b-1)" in { + PacketCoding.DecodePacket(string_b1).require match { + case SquadMembershipResponse(unk1, unk2, unk3, unk4, unk5, unk6, unk7, unk8) => + unk1 mustEqual SquadRequestType.PlatoonCancel + unk2 mustEqual 0 + unk3 mustEqual 1 + unk4 mustEqual 30910985L + unk5.contains(41578085L) mustEqual true + unk6 mustEqual "SAraisVanu" + unk7 mustEqual false + unk8.contains(None) mustEqual true + case _ => + ko + } + } + + "encode (0-1)" in { + val msg = SquadMembershipResponse(SquadRequestType.Invite, 0, 0, 42771010L, Some(1300870L), "HofD", false, None) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_01 + } + + "encode (0-2)" in { + val msg = SquadMembershipResponse(SquadRequestType.Invite, 0, 0, 1176612L, Some(1004937L), "SPOILERS", true, None) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_02 + } + + "encode (1-1)" in { + val msg = SquadMembershipResponse(SquadRequestType.Unk01, 19, 0, 41530025L, Some(0L), "", true, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_11 + } + + "encode (1-2)" in { + val msg = SquadMembershipResponse(SquadRequestType.Unk01, 18, 0, 41578085L, Some(0L), "", true, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_12 + } + + "encode (2-1)" in { + val msg = SquadMembershipResponse(SquadRequestType.Accept, 0, 0, 1300870L, Some(42771010L), "VirusGiver", true, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_21 + } + + "encode (2-2)" in { + val msg = SquadMembershipResponse(SquadRequestType.Accept, 0, 0, 1300870L, Some(30736877L), "VirusGiver", true, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_22 + } + + "encode (3-1)" in { + val msg = SquadMembershipResponse(SquadRequestType.Reject, 0, 3, 31035057L, Some(42771010L), "HMFIC", false, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_31 + } + + "encode (3-2)" in { + val msg = SquadMembershipResponse(SquadRequestType.Reject, 0, 2, 31106913L, Some(42771010L), "DAN1111", false, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_32 + } + + "encode (4-1)" in { + val msg = SquadMembershipResponse(SquadRequestType.Cancel, 0, 2, 41578085L, Some(41607133L), "SAraisVanu", false, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_41 + } + + "encode (4-2)" in { + val msg = SquadMembershipResponse(SquadRequestType.Cancel, 0, 2, 41607396L, Some(41324011L), "AirInjector", false, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_42 + } + + "encode (5-1)" in { + val msg = SquadMembershipResponse(SquadRequestType.Leave, 0, 1, 1176612L, Some(1176612L), "CCRIDER", true, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_51 + } + + "encode (7-1)" in { + val msg = SquadMembershipResponse(SquadRequestType.Disband, 2, 0, 1176612L, Some(0L), "", true, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_71 + } + + "encode (7-2)" in { + val msg = SquadMembershipResponse(SquadRequestType.Disband, 0, 0, 41578085L, Some(30910985L), "Wizkid45", true, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_72 + } + + "encode (8-1)" in { + val msg = SquadMembershipResponse(SquadRequestType.PlatoonInvite, 0, 0, 30910985L, Some(41578085L), "Wizkid45", false, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_81 + } + + "encode (9-1)" in { + val msg = SquadMembershipResponse(SquadRequestType.PlatoonAccept, 0, 0, 297025L, Some(41605002L), "", true, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_91 + } + + "encode (9-2)" in { + val msg = SquadMembershipResponse(SquadRequestType.PlatoonAccept, 0, 0, 30910985L, Some(41578085L), "Wizkid45", false, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_92 + } + + "encode (b-1)" in { + val msg = SquadMembershipResponse(SquadRequestType.PlatoonCancel, 0, 1, 30910985L, Some(41578085L), "SAraisVanu", false, Some(None)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string_b1 + } + } +}