From d75ab1166f77313adefc86acd9a7251f5c26d74b Mon Sep 17 00:00:00 2001 From: Resaec Date: Fri, 26 Apr 2024 02:16:40 +0200 Subject: [PATCH] Add OutfitMemberEvent packet Fix and improve some other Outfit packets and tests --- .../psforever/packet/GamePacketOpcode.scala | 2 +- .../psforever/packet/game/OutfitEvent.scala | 296 ++++++++++++++---- .../packet/game/OutfitMemberEvent.scala | 57 ++++ .../packet/game/OutfitMemberUpdate.scala | 20 +- src/test/scala/game/OutfitEventTest.scala | 286 ++++++++++++++++- .../scala/game/OutfitMemberEventTest.scala | 58 ++++ 6 files changed, 638 insertions(+), 81 deletions(-) create mode 100644 src/main/scala/net/psforever/packet/game/OutfitMemberEvent.scala create mode 100644 src/test/scala/game/OutfitMemberEventTest.scala diff --git a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 2dda57664..caf6cb20e 100644 --- a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -474,7 +474,7 @@ object GamePacketOpcode extends Enumeration { case 0x8f => game.OutfitEvent.decode // OPCODES 0x90-9f - case 0x90 => noDecoder(OutfitMemberEvent) + case 0x90 => game.OutfitMemberEvent.decode case 0x91 => game.OutfitMemberUpdate.decode case 0x92 => game.PlanetsideStringAttributeMessage.decode case 0x93 => game.DataChallengeMessage.decode diff --git a/src/main/scala/net/psforever/packet/game/OutfitEvent.scala b/src/main/scala/net/psforever/packet/game/OutfitEvent.scala index 98ead8850..c84847436 100644 --- a/src/main/scala/net/psforever/packet/game/OutfitEvent.scala +++ b/src/main/scala/net/psforever/packet/game/OutfitEvent.scala @@ -3,6 +3,7 @@ package net.psforever.packet.game import net.psforever.packet.GamePacketOpcode.Type import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import net.psforever.types.PlanetSideGUID import scodec.{Attempt, Codec, Err} import scodec.bits.BitVector import scodec.codecs._ @@ -10,7 +11,7 @@ import shapeless.{::, HNil} final case class OutfitEvent( request_type: OutfitEvent.RequestType.Type, - unk1: Int, + outfit_guid: PlanetSideGUID, action: OutfitEventAction ) extends PlanetSideGamePacket { type Packet = OutfitEvent @@ -24,33 +25,79 @@ abstract class OutfitEventAction(val code: Int) object OutfitEventAction { - final case class CreatedOutfit( + final case class OutfitRankNames( + rank1: String, + rank2: String, + rank3: String, + rank4: String, + rank5: String, + rank6: String, + rank7: String, + rank8: String, + ) + + final case class OutfitInfo( + unk1: Int, unk2: Int, - unk3: Int, - unk4: Int, outfit_name: String, unk6: Long, unk7: Long, - members: Int, + member_count: Int, unk9: Int, - unk10: String, - unk11: String, - unk12: String, - unk13: String, - unk14: String, - unk15: String, - unk16: String, - unk17: String, - unk18: String, - unk19: Int, + outfit_rank_names: OutfitRankNames, + motd: String, + owner_guid: PlanetSideGUID, // ? unk20: Int, - unk21: Long, - unk22: Long, + unk21: Int, + unk21_2: Int, + created_timestamp: Long, unk23: Long, unk24: Long, - unk25: Int, + unk25: Long, + u123: Int, + ) + + final case class Unk0( + outfitInfo: OutfitInfo + ) extends OutfitEventAction(code = 0) + + final case class Unk1( + unk0: Int, + unk1: Int, + unk2: Int, + unk3: Boolean, + ) extends OutfitEventAction(code = 1) + + final case class Unk2( + outfitInfo: OutfitInfo, + ) extends OutfitEventAction(code = 2) + + final case class Unk3( + unk0: Int, + unk1: Int, + unk2: Int, + unk3: Boolean, + data: BitVector, + ) extends OutfitEventAction(code = 3) + + final case class Unk4( + unk0: Int, + unk1: Int, + unk2: Int, + unk3: Int, + unk4: Boolean, + data: BitVector, ) extends OutfitEventAction(code = 4) + final case class Unk5( + unk0: Int, + unk1: Int, + unk2: Int, + unk3: Int, + unk4: Boolean, + data: BitVector, + ) extends OutfitEventAction(code = 5) + final case class Unknown(badCode: Int, data: BitVector) extends OutfitEventAction(badCode) /** @@ -60,42 +107,151 @@ object OutfitEventAction { object Codecs { private val everFailCondition = conditional(included = false, bool) - val CreatedOutfitCodec: Codec[CreatedOutfit] = - (uint8L :: - uint4L :: - uintL(3) :: + private val OutfitRankNamesCodec: Codec[OutfitRankNames] = ( + PacketHelpers.encodedWideString :: + PacketHelpers.encodedWideString :: + PacketHelpers.encodedWideString :: + PacketHelpers.encodedWideString :: + PacketHelpers.encodedWideString :: + PacketHelpers.encodedWideString :: + PacketHelpers.encodedWideString :: + PacketHelpers.encodedWideString + ).xmap[OutfitRankNames]( + { + case u0 :: u1 :: u2 :: u3 :: u4 :: u5 :: u6 :: u7 :: HNil => + OutfitRankNames(u0, u1, u2, u3, u4, u5, u6, u7) + }, + { + case OutfitRankNames(u0, u1, u2, u3, u4, u5, u6, u7) => + u0 :: u1 :: u2 :: u3 :: u4 :: u5 :: u6 :: u7 :: HNil + } + ) + + private val InfoCodec: Codec[OutfitInfo] = ( + uint8L :: + uint8L :: PacketHelpers.encodedWideStringAligned(5) :: uint32L :: uint32L :: uint16L :: uint16L :: - PacketHelpers.encodedString :: - PacketHelpers.encodedString :: - PacketHelpers.encodedString :: - PacketHelpers.encodedString :: - PacketHelpers.encodedString :: - PacketHelpers.encodedString :: - PacketHelpers.encodedString :: - PacketHelpers.encodedString :: - PacketHelpers.encodedString :: - uint16L :: - uint16L :: + OutfitRankNamesCodec :: + PacketHelpers.encodedWideString :: + PlanetSideGUID.codec :: + uint16L :: // + uint8L :: // bool somewhere here + uintL(1) :: // + ("created_timestamp" | uint32L) :: uint32L :: uint32L :: uint32L :: - uint32L :: - uint16L - ).xmap[CreatedOutfit]( + uintL(7) + ).xmap[OutfitInfo]( { - case u2 :: u3 :: u4 :: outfit_name :: u6 :: u7 :: members :: u9 :: u10 :: u11 :: u12 :: u13 :: u14 :: u15 :: u16 :: u17 :: u18 :: u19 :: u20 :: u21 :: u22 :: u23 :: u24 :: u25 :: HNil => - CreatedOutfit(u2, u3, u4, outfit_name, u6, u7, members, u9, u10, u11, u12, u13, u14, u15, u16, u17, u18, u19, u20, u21, u22, u23, u24, u25) + case u1 :: u2 :: outfit_name :: u6 :: u7 :: member_count :: u9 :: outfit_rank_names :: motd :: u19 :: u20 :: u21 :: u21_2 :: created_timestamp :: u23 :: u24 :: u25 :: u123 :: HNil => + OutfitInfo(u1, u2, outfit_name, u6, u7, member_count, u9, outfit_rank_names, motd, u19, u20, u21, u21_2, created_timestamp, u23, u24, u25, u123) }, { - case CreatedOutfit(u2, u3, u4, outfit_name, u6, u7, members, u9, u10, u11, u12, u13, u14, u15, u16, u17, u18, u19, u20, u21, u22, u23, u24, u25) => - u2 :: u3 :: u4 :: outfit_name :: u6 :: u7 :: members :: u9 :: u10 :: u11 :: u12 :: u13 :: u14 :: u15 :: u16 :: u17 :: u18 :: u19 :: u20 :: u21 :: u22 :: u23 :: u24 :: u25 :: HNil + case OutfitInfo(u1, u2, outfit_name, u6, u7, member_count, u9, outfit_rank_names, motd, u19, u20, u21, u21_2, created_timestamp, u23, u24, u25, u123) => + u1 :: u2 :: outfit_name :: u6 :: u7 :: member_count :: u9 :: outfit_rank_names :: motd :: u19 :: u20 :: u21 :: u21_2 :: created_timestamp :: u23 :: u24 :: u25 :: u123 :: HNil } ) + val Unk0Codec: Codec[Unk0] = ( + InfoCodec + ).xmap[Unk0]( + { + case info => + Unk0(info) + }, + { + case Unk0(info) => + info + } + ) + + val Unk1Codec: Codec[Unk1] = ( + uint8L :: + uint8L :: + uint4L :: + bool + ).xmap[Unk1]( + { + case u0 :: u1 :: u2 :: u3 :: HNil => + Unk1(u0, u1, u2, u3) + }, + { + case Unk1(u0, u1, u2, u3) => + u0 :: u1 :: u2 :: u3 :: HNil + } + ) + + val Unk2Codec: Codec[Unk2] = ( + InfoCodec + ).xmap[Unk2]( + { + case info => + Unk2(info) + }, + { + case Unk2(info) => + info + } + ) + + val Unk3Codec: Codec[Unk3] = ( + uint8L :: + uint8L :: + uint4L :: + bool :: + bits + ).xmap[Unk3]( + { + case u0 :: u1 :: u2 :: u3 :: data :: HNil => + Unk3(u0, u1, u2, u3, data) + }, + { + case Unk3(u0, u1, u2, u3, data) => + u0 :: u1 :: u2 :: u3 :: data :: HNil + } + ) + + val Unk4Codec: Codec[Unk4] = ( + uint16L :: + uint16L :: + uint16L :: + uint4L :: + bool :: + bits + ).xmap[Unk4]( + { + case u0 :: u1 :: u2 :: u3 :: u4 :: data :: HNil => + Unk4(u0, u1, u2, u3, u4, data) + }, + { + case Unk4(u0, u1, u2, u3, u4, data) => + u0 :: u1 :: u2 ::u3 :: u4 :: data :: HNil + } + ) + + val Unk5Codec: Codec[Unk5] = ( + uint16L :: + uint16L :: + uint16L :: + uint4L :: + bool :: + bits + ).xmap[Unk5]( + { + case u0 :: u1 :: u2 :: u3 :: u4 :: data :: HNil => + Unk5(u0, u1, u2, u3, u4, data) + }, + { + case Unk5(u0, u1, u2, u3, u4, data) => + u0 :: u1 :: u2 :: u3 :: u4 :: data :: HNil + } + ) + /** * A common form for known action code indexes with an unknown purpose and transformation is an "Unknown" object. * @@ -129,16 +285,36 @@ object OutfitEvent extends Marshallable[OutfitEvent] { object RequestType extends Enumeration { type Type = Value - val unk0: RequestType.Value = Value(0) - val unk1: RequestType.Value = Value(1) - val unk2: RequestType.Value = Value(2) - val unk3: RequestType.Value = Value(3) - val CreatedOutfit: RequestType.Value = Value(4) - val unk5: RequestType.Value = Value(5) - val Unk6: RequestType.Value = Value(6) - val Unk7: RequestType.Value = Value(7) + val Unk0: RequestType.Value = Value(0) + val Unk1: RequestType.Value = Value(1) // send in SMP after a f load of OutfitMemberEvent + val Unk2: RequestType.Value = Value(2) // send after creating an outfit + val Unk3: RequestType.Value = Value(3) // below + val Unk4: RequestType.Value = Value(4) + val Unk5: RequestType.Value = Value(5) + val unk6: RequestType.Value = Value(6) + val unk7: RequestType.Value = Value(7) - implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uint4L) + /* + unk3 -- #66162 PSCap-2016-02-28_02-58-10-PM.txt + + MP( + SMP( + MPEx( + OutfitMembershipResponse, + OutfitEvent, + SquadMemberEvent + ) + ), + SMP( + MPEx( + PlanetsideAttributeMessage x 3 + PlanetsideStringAttributeMessage + ) + ) + ) + ) + */ + + implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(3)) } private def selectFromType(code: Int): Codec[OutfitEventAction] = { @@ -146,12 +322,12 @@ object OutfitEvent extends Marshallable[OutfitEvent] { import scala.annotation.switch ((code: @switch) match { - case 0 => unknownCodec(action = code) - case 1 => unknownCodec(action = code) - case 2 => unknownCodec(action = code) - case 3 => unknownCodec(action = code) - case 4 => CreatedOutfitCodec // sent after /outfitcreate - case 5 => unknownCodec(action = code) + case 0 => Unk0Codec + case 1 => Unk1Codec + case 2 => Unk2Codec // sent after /outfitcreate ? + case 3 => Unk3Codec + case 4 => Unk4Codec + case 5 => Unk5Codec case 6 => unknownCodec(action = code) case 7 => unknownCodec(action = code) @@ -161,17 +337,17 @@ object OutfitEvent extends Marshallable[OutfitEvent] { implicit val codec: Codec[OutfitEvent] = ( ("request_type" | RequestType.codec) >>:~ { request_type => - ("avatar_guid" | uint16L) :: + ("outfit_guid" | PlanetSideGUID.codec) :: ("action" | selectFromType(request_type.id)) } ).xmap[OutfitEvent]( { - case request_type :: avatar_guid :: action :: HNil => - OutfitEvent(request_type, avatar_guid, action) + case request_type :: outfit_guid :: action :: HNil => + OutfitEvent(request_type, outfit_guid, action) }, { - case OutfitEvent(request_type, avatar_guid, action) => - request_type :: avatar_guid :: action :: HNil + case OutfitEvent(request_type, outfit_guid, action) => + request_type :: outfit_guid :: action :: HNil } ) } diff --git a/src/main/scala/net/psforever/packet/game/OutfitMemberEvent.scala b/src/main/scala/net/psforever/packet/game/OutfitMemberEvent.scala new file mode 100644 index 000000000..d3c33c4bd --- /dev/null +++ b/src/main/scala/net/psforever/packet/game/OutfitMemberEvent.scala @@ -0,0 +1,57 @@ +// Copyright (c) 2024 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import net.psforever.types.PlanetSideGUID +import scodec.Codec +import scodec.codecs._ +import shapeless.{::, HNil} + +final case class OutfitMemberEvent( + unk00: Int, + outfit_id: PlanetSideGUID, + unk1: Int, + unk2: Int, + unk3: Int, + member_name: String, + unk7: Int, + unk8: Int, + unk9: Int, + unk10: Int, + unk12: Int, + unk13: Int, + unk14: Int, + unk15: Int, +) extends PlanetSideGamePacket { + type Packet = OutfitMemberEvent + def opcode = GamePacketOpcode.OutfitMemberEvent + def encode = OutfitMemberEvent.encode(this) +} + +object OutfitMemberEvent extends Marshallable[OutfitMemberEvent] { + implicit val codec: Codec[OutfitMemberEvent] = ( + ("unk00" | uintL(2)) :: + ("outfit_id" | PlanetSideGUID.codec) :: + ("unk1" | uint16L) :: + ("unk2" | uint16L) :: + ("unk3" | uint16L) :: + ("member_name" | PacketHelpers.encodedWideStringAligned(6)) :: + ("unk7" | uint8L) :: + ("unk8" | uint8L) :: + ("unk9" | uint16L) :: + ("unk10" | uint8L) :: + ("unk12" | uint8L) :: + ("unk13" | uint8L) :: + ("unk14" | uint8L) :: + ("unk15" | uint8L) + ).xmap[OutfitMemberEvent]( + { + case unk00 :: outfit_id :: u1 :: u2 :: u3 :: member_name :: u7 :: u8 :: u9 :: u10 :: u12 :: u13 :: u14 :: u15 :: HNil => + OutfitMemberEvent(unk00, outfit_id, u1, u2, u3, member_name, u7, u8, u9, u10, u12, u13, u14, u15) + }, + { + case OutfitMemberEvent(unk00, outfit_id, u1, u2, u3, member_name, u7, u8, u9, u10, u12, u13, u14, u15) => + unk00 :: outfit_id :: u1 :: u2 :: u3 :: member_name :: u7 :: u8 :: u9 :: u10 :: u12 :: u13 :: u14 :: u15 :: HNil + } + ) +} diff --git a/src/main/scala/net/psforever/packet/game/OutfitMemberUpdate.scala b/src/main/scala/net/psforever/packet/game/OutfitMemberUpdate.scala index b4012fc06..8b28406ad 100644 --- a/src/main/scala/net/psforever/packet/game/OutfitMemberUpdate.scala +++ b/src/main/scala/net/psforever/packet/game/OutfitMemberUpdate.scala @@ -1,13 +1,18 @@ // Copyright (c) 2024 PSForever package net.psforever.packet.game -import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} import net.psforever.types.PlanetSideGUID import scodec.Codec import scodec.codecs._ import shapeless.{::, HNil} -final case class OutfitMemberUpdate(outfit_id: PlanetSideGUID, unk1: Int, unk2: Int, unk3: Int) extends PlanetSideGamePacket { +final case class OutfitMemberUpdate( + outfit_guid: PlanetSideGUID, + unk1: Int, + unk2: Int, + unk3: Int, +) extends PlanetSideGamePacket { type Packet = OutfitMemberUpdate def opcode = GamePacketOpcode.OutfitMemberUpdate def encode = OutfitMemberUpdate.encode(this) @@ -15,19 +20,18 @@ final case class OutfitMemberUpdate(outfit_id: PlanetSideGUID, unk1: Int, unk2: object OutfitMemberUpdate extends Marshallable[OutfitMemberUpdate] { implicit val codec: Codec[OutfitMemberUpdate] = ( - ("outfit_id" | PlanetSideGUID.codec) :: + ("outfit_guid" | PlanetSideGUID.codec) :: ("unk1" | uint16L) :: ("unk2" | uint16L) :: ("unk3" | uint8L) ).xmap[OutfitMemberUpdate]( { - case u0 :: u1 :: u2 :: u3 :: HNil => - OutfitMemberUpdate(u0, u1, u2, u3) + case outfit_guid :: u1 :: u2 :: u3 :: HNil => + OutfitMemberUpdate(outfit_guid, u1, u2, u3) }, { - case OutfitMemberUpdate(u0, u1, u2, u3) => - u0 :: u1 :: u2 :: u3 :: HNil + case OutfitMemberUpdate(outfit_guid, u1, u2, u3) => + outfit_guid :: u1 :: u2 :: u3 :: HNil } ) -// ).as[OutfitMemberUpdate] } diff --git a/src/test/scala/game/OutfitEventTest.scala b/src/test/scala/game/OutfitEventTest.scala index 38494ac61..2e1ab327f 100644 --- a/src/test/scala/game/OutfitEventTest.scala +++ b/src/test/scala/game/OutfitEventTest.scala @@ -1,32 +1,294 @@ -// Copyright (c) 2023 PSForever +// Copyright (c) 2024 PSForever package game import net.psforever.packet._ import net.psforever.packet.game.OutfitEvent.RequestType import net.psforever.packet.game.OutfitEventAction._ import net.psforever.packet.game._ +import net.psforever.types.PlanetSideGUID import org.specs2.mutable._ import scodec.bits._ class OutfitEventTest extends Specification { - val created_ABC = hex"8f 4 0201 feff 2e 0 50006c0061006e006500740053006900640065005f0046006f00720065007600650072005f00560061006e007500 00000000 00000000 0100 0000 80 80 80 80 80 80 80 80 80 0070 4982 00000000 00000000 00000000 00000000 0000" + val unk0_ABC: ByteVector = ByteVector.fromValidHex( + "8f 1 a8c2 0001" + // packet head + "2a 0 42006c00610063006b002000410072006d006f0072006500640020005200650061007000650072007300" + // Black Armored Reapers + "1d9c4d0d" + + "1d9c4d0d" + + "ab00 0000" + + "88 44006f00670020004d00650061007400" + // Dog Meat + "87 5200750073007300690061006e00" + // Russian + "80" + // + "80" + // + "8d 5300710075006100640020004c00650061006400650072007300" + // Squad Leaders + "91 41006300740069006e006700200043006f006d006d0061006e006400650072007300" + // Acting Commanders + "87 5200650061007000650072007300" + // Reapers + "80" + // + "00" + + "9c 5c0023003000300030003000660066004d0075006d0062006c00650020005c00230030003000330033006600660049006e0066006f0020005c0023003000300036003600660066006900730020005c0023003000300039003900660066007400680065006d006f006f00730065002e00740079007000650066007200610067002e0063006f006d0020005c00230030003000630063006600660070006f007200740020005c002300300030006600660066006600390033003500300020005c0023003000300063006300660066006a006f0069006e0020005c0023003000300039003900660066006900740020005c0023003000300036003600660066006f00720020005c0023003000300033003300660066006200650020005c0023003000300030003000660066006b00690063006b00650064002e00" + + "0f80" + + "0000 00737296 24000000 00000000 00000000 0000") + val unk1_ABC: ByteVector = hex"8f 2 302a 10 00 0" + val unk2_ABC: ByteVector = ByteVector.fromValidHex( + "8f 4 0201 feff" + + "2e 0 50006c0061006e006500740053006900640065005f0046006f00720065007600650072005f00560061006e007500" + // PlanetSide_Forever_Vanu + "00000000" + + "00000000" + + "0100 0000" + + "80" + + "80" + + "80" + + "80" + + "80" + + "80" + + "80" + + "80" + + "80" + + "0070" + + "4982 00000000 00000000 00000000 00000000 0000") + val unk3_ABC: ByteVector = hex"8f 6 0201 fe fe 0" + val unk4_ABC: ByteVector = hex"8f 8 0201 fefe a02a 1000 0" + val unk5_ABC: ByteVector = hex"8f a 0201 fefe 0400 0000 0" - - "decode CreatedOutfit ABC" in { - PacketCoding.decodePacket(created_ABC).require match { - case OutfitEvent(request_type, unk1, action) => - request_type mustEqual RequestType.CreatedOutfit - unk1 mustEqual 258 - action mustEqual CreatedOutfit(unk2 = 254, unk3 = 15, unk4 = 7, outfit_name = "PlanetSide_Forever_Vanu", unk6 = 0, unk7 = 0, members = 1, unk9 = 0, "", "", "", "", "", "", "", "", "", 28672, 33353, 0, 0, 0, 0, 0) + "decode Unk0 ABC" in { + PacketCoding.decodePacket(unk0_ABC).require match { + case OutfitEvent(request_type, outfit_guid, action) => + request_type mustEqual RequestType.Unk0 + outfit_guid mustEqual PlanetSideGUID(25044) + action mustEqual Unk0( + OutfitInfo( + unk1 = 0, + unk2 = 0, + outfit_name = "Black Armored Reapers", + unk6 = 223190045, + unk7 = 223190045, + member_count = 171, + unk9 = 0, + OutfitRankNames("Dog Meat","Russian","","","Squad Leaders","Acting Commanders","Reapers",""), + "\\#0000ffMumble \\#0033ffInfo \\#0066ffis \\#0099ffthemoose.typefrag.com \\#00ccffport \\#00ffff9350 \\#00ccffjoin \\#0099ffit \\#0066ffor \\#0033ffbe \\#0000ffkicked.", + PlanetSideGUID(32783), + 0, + 0, + 0, + 1210901990, + 0, + 0, + 0, + 0, + ) + ) case _ => ko } } - "encode CreatedOutfit ABC" in { - val msg = OutfitEvent(RequestType.CreatedOutfit, 258, CreatedOutfit(unk2 = 254, unk3 = 15, unk4 = 7, outfit_name = "PlanetSide_Forever_Vanu", unk6 = 0, unk7 = 0, members = 1, unk9 = 0, "", "", "", "", "", "", "", "", "", 28672, 33353, 0, 0, 0, 0, 0)) + "encode Unk0 ABC" in { + val msg = OutfitEvent( + RequestType.Unk0, + PlanetSideGUID(25044), + Unk0( + OutfitInfo( + unk1 = 0, + unk2 = 0, + outfit_name = "Black Armored Reapers", + unk6 = 223190045, + unk7 = 223190045, + member_count = 171, + unk9 = 0, + OutfitRankNames("Dog Meat","Russian","","","Squad Leaders","Acting Commanders","Reapers",""), + "\\#0000ffMumble \\#0033ffInfo \\#0066ffis \\#0099ffthemoose.typefrag.com \\#00ccffport \\#00ffff9350 \\#00ccffjoin \\#0099ffit \\#0066ffor \\#0033ffbe \\#0000ffkicked.", + PlanetSideGUID(32783), + 0, + 0, + 0, + 1210901990, + 0, + 0, + 0, + 0, + ) + ) + ) val pkt = PacketCoding.encodePacket(msg).require.toByteVector - pkt mustEqual created_ABC + pkt mustEqual unk0_ABC + } + + "decode Unk1 ABC" in { + PacketCoding.decodePacket(unk1_ABC).require match { + case OutfitEvent(request_type, outfit_guid, action) => + request_type mustEqual RequestType.Unk1 + outfit_guid mustEqual PlanetSideGUID(5400) + action mustEqual Unk1(unk0 = 8, unk1 = 0, unk2 = 0, unk3 = false) + case _ => + ko + } + } + + "encode Unk1 ABC" in { + val msg = OutfitEvent( + RequestType.Unk1, + PlanetSideGUID(5400), + Unk1( + unk0 = 8, + unk1 = 0, + unk2 = 0, + unk3 = false, + ) + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual unk1_ABC + } + + "decode Unk2 ABC" in { + PacketCoding.decodePacket(unk2_ABC).require match { + case OutfitEvent(request_type, outfit_guid, action) => + request_type mustEqual RequestType.Unk2 + outfit_guid mustEqual PlanetSideGUID(1) + action mustEqual Unk2(OutfitInfo(unk1 = 255, unk2 = 127, outfit_name = "PlanetSide_Forever_Vanu", + unk6 = 0, unk7 = 0, member_count = 1, unk9 = 0, OutfitRankNames("","","","","","","",""), + "", PlanetSideGUID(28672), 33353, 0, 0, 0, 0, 0, 0, 0)) + case _ => + ko + } + } + + "encode Unk2 ABC" in { + val msg = OutfitEvent( + RequestType.Unk2, + PlanetSideGUID(1), + Unk2( + OutfitInfo( + unk1 = 255, + unk2 = 127, + outfit_name = "PlanetSide_Forever_Vanu", + unk6 = 0, + unk7 = 0, + member_count = 1, + unk9 = 0, + OutfitRankNames("","","","","","","",""), + "", + PlanetSideGUID(28672), + 33353, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + ) + ) + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual unk2_ABC + } + + "decode Unk3 ABC" in { + PacketCoding.decodePacket(unk3_ABC).require match { + case OutfitEvent(request_type, outfit_guid, action) => + request_type mustEqual RequestType.Unk3 + outfit_guid mustEqual PlanetSideGUID(1) + action mustEqual Unk3( + unk0 = 255, + unk1 = 127, + unk2 = 0, + unk3 = false, + BitVector.fromValidHex("") + ) + case _ => + ko + } + } + + "encode Unk3 ABC" in { + val msg = OutfitEvent( + RequestType.Unk3, + PlanetSideGUID(1), + Unk3( + unk0 = 255, + unk1 = 127, + unk2 = 0, + unk3 = false, + BitVector.fromValidHex("") + ) + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual unk3_ABC + } + + "decode Unk4 ABC" in { + PacketCoding.decodePacket(unk4_ABC).require match { + case OutfitEvent(request_type, outfit_guid, action) => + request_type mustEqual RequestType.Unk4 + outfit_guid mustEqual PlanetSideGUID(1) + action mustEqual Unk4( + unk0 = 32767, + unk1 = 5456, + unk2 = 8, + 0, + unk4 = false, + BitVector.fromValidHex("") + ) + case _ => + ko + } + } + + "encode Unk4 ABC" in { + val msg = OutfitEvent( + RequestType.Unk4, + PlanetSideGUID(1), + Unk4( + unk0 = 32767, + unk1 = 5456, + unk2 = 8, + unk3 = 0, + unk4 = false, + BitVector.fromValidHex("") + ) + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual unk4_ABC + } + + "decode Unk5 ABC" in { + PacketCoding.decodePacket(unk5_ABC).require match { + case OutfitEvent(request_type, outfit_guid, action) => + request_type mustEqual RequestType.Unk5 + outfit_guid mustEqual PlanetSideGUID(1) + action mustEqual Unk5( + unk0 = 32767, + unk1 = 2, + unk2 = 0, + unk3 = 0, + unk4 = false, + BitVector.fromValidHex("") // OR f88c2a0417c1a06101001f20f4b8c00000404090ac9c6745dea88cadf0f810e03e0200f92 with bool at the back + ) + case _ => + ko + } + } + + "encode Unk5 ABC" in { + val msg = OutfitEvent( + RequestType.Unk5, + PlanetSideGUID(1), + Unk5( + unk0 = 32767, + unk1 = 2, + unk2 = 0, + unk3 = 0, + unk4 = false, + BitVector.fromValidHex("") + ) + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual unk5_ABC } } diff --git a/src/test/scala/game/OutfitMemberEventTest.scala b/src/test/scala/game/OutfitMemberEventTest.scala new file mode 100644 index 000000000..a4a27c8e7 --- /dev/null +++ b/src/test/scala/game/OutfitMemberEventTest.scala @@ -0,0 +1,58 @@ +// Copyright (c) 2024 PSForever +package game + +import net.psforever.packet._ + +import net.psforever.packet.game._ +import net.psforever.types.PlanetSideGUID +import org.specs2.mutable._ +import scodec.bits._ + +class OutfitMemberEventTest extends Specification { + + //val unk0_ABC: ByteVector = hex"90 3518 4000 1a4e 4100 2 180 450078007000650072007400 8483 07e0 119d bfe0 70" // 0x90048640001030c28022404c0061007a00650072003100390038003200f43a45e00b4c604010 +val unk0_ABC: ByteVector = hex"90 048640001030c28022404c0061007a00650072003100390038003200f43a45e00b4c604010" + "decode Unk0 ABC" in { + PacketCoding.decodePacket(unk0_ABC).require match { + case OutfitMemberEvent(unk00, outfit_guid, unk1, unk2, unk3, member_name, unk7, unk8, unk9, unk10, unk12, unk13, unk14, unk15) => + unk00 mustEqual 0 + outfit_guid mustEqual PlanetSideGUID(6418) + unk1 mustEqual 0 + unk2 mustEqual 49984 + unk3 mustEqual 10 + member_name mustEqual "Lazer1982" + unk7 mustEqual 244 + unk8 mustEqual 58 + unk9 mustEqual 57413 + unk10 mustEqual 11 + unk12 mustEqual 76 + unk13 mustEqual 96 + unk14 mustEqual 64 + unk15 mustEqual 16 + case _ => + ko + } + } + + "encode Unk0 ABC" in { + val msg = OutfitMemberEvent( + unk00 = 0, + outfit_id = PlanetSideGUID(6418), + unk1 = 0, + unk2 = 49984, + unk3 = 10, + member_name = "Lazer1982", + unk7 = 244, + unk8 = 58, + unk9 = 57413, + unk10 = 11, + unk12 = 76, + unk13 = 96, + unk14 = 64, + unk15 = 16, + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual unk0_ABC + } +}