From d450a1b6e5c536d76bc9f23b5604dcdfa0df64d0 Mon Sep 17 00:00:00 2001 From: Resaec Date: Wed, 20 Aug 2025 00:03:17 +0200 Subject: [PATCH] OutfitListEvent ListElementOutfit decoded --- .../psforever/packet/game/OutfitEvent.scala | 29 +--- .../packet/game/OutfitListEvent.scala | 162 ++++++++++++++++-- .../psforever/packet/game/OutfitRequest.scala | 4 +- src/test/scala/game/OutfitEventTest.scala | 16 +- src/test/scala/game/OutfitListEventTest.scala | 35 ++-- 5 files changed, 174 insertions(+), 72 deletions(-) diff --git a/src/main/scala/net/psforever/packet/game/OutfitEvent.scala b/src/main/scala/net/psforever/packet/game/OutfitEvent.scala index 90cffd4b..3117bbb4 100644 --- a/src/main/scala/net/psforever/packet/game/OutfitEvent.scala +++ b/src/main/scala/net/psforever/packet/game/OutfitEvent.scala @@ -38,8 +38,8 @@ object OutfitEventAction { final case class OutfitInfo( outfit_name: String, - unk6: Long, - unk7: Long, + outfit_points1: Long, + outfit_points2: Long, // same as outfit_points1 member_count: Int, unk9: Int, outfit_rank_names: OutfitRankNames, @@ -276,31 +276,6 @@ object OutfitEvent extends Marshallable[OutfitEvent] { val unk6: RequestType.Value = Value(6) val unk7: RequestType.Value = Value(7) - /* - - OutfitEvent(Unk0, ValidPlanetSideGUID(18361), Unk0(OutfitInfo(0, 0, The Black Ravens, 338420223, 338420223, 433, 0, OutfitRankNames(Corporal (No Ventrilo), Sergeant - SGT, Advance Medical, , Master Sgt - MSG, Captain, Trusted Officer, OutFit Leader), TBR website..... http://trravens.darkbb.com ventrilo info: evolve.typefrag.com port: 45694 (vent pw dotaftw) Channel PW: zeroenigma : if you guys wants to contact me, my email is zero_overkill99@yahoo.com, ValidPlanetSideGUID(32787), 0, 0, 0, 1133571390, 0, 0, 0, 0))) - OutfitEvent(Unk2, ValidPlanetSideGUID(18361), Unk2(OutfitInfo(0, 0, The Black Ravens, 338420486, 338420486, 433, 0, OutfitRankNames(Corporal (No Ventrilo), Sergeant - SGT, Advance Medical, , Master Sgt - MSG, Captain, Trusted Officer, OutFit Leader), TBR website..... http://trravens.darkbb.com ventrilo info: evolve.typefrag.com port: 45694 (vent pw dotaftw) Channel PW: zeroenigma : if you guys wants to contact me, my email is zero_overkill99@yahoo.com, ValidPlanetSideGUID(32787), 0, 0, 0, 1133571390, 0, 0, 0, 0))) - - - 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)) } diff --git a/src/main/scala/net/psforever/packet/game/OutfitListEvent.scala b/src/main/scala/net/psforever/packet/game/OutfitListEvent.scala index 52ea9929..c040f2e5 100644 --- a/src/main/scala/net/psforever/packet/game/OutfitListEvent.scala +++ b/src/main/scala/net/psforever/packet/game/OutfitListEvent.scala @@ -3,19 +3,14 @@ package net.psforever.packet.game import net.psforever.packet.GamePacketOpcode.Type import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} -import scodec.bits.BitVector +import scodec.{Attempt, Codec, Err} +import scodec.bits.{BitVector, ByteVector} import scodec.codecs._ -import scodec.{Attempt, Codec} - -// 98 5ec300000d01a020004000001 12056002e0053002e0053002e0055002e004400 845400680069006f009 85ee300001e2b 858000800000110041002e0027002e0041002e0027002e00 8448006900720075009 84003200005a540000060000011a0530065006300720065007400200043006800690065006600730085530069006c00610073009840a32000001953476fe0c00011c041007a0075007200650020005400770069006c006900670068007400874600720061006e0063006b006f009840c3200000d3a4c000c00000106030002e006f0085410074006c0061007300984183200011d9296000c0000011e0570061007200720069006f007200270073002000430072006500650064008653006500760061006b00690098442320001bf40e000080000013203100330033003700740068002000410072006d006f0072006500640020004400690076006900730069006f006e002d004b008548006f0073002d004b009844c320001b3d2c200060000012a03300330031007300740020004d0069006e006e00650073006f0074006100200054007200690062006500864d006100670069002d0045009846c3200009e206c00040000010c04100720065006100350031008942006c00610063 +import shapeless.{::, HNil} final case class OutfitListEvent( - outfit_score: Long, - unk1: Long, - unk2: Long, - unk3: Int, - outfit_name: String, - outfit_leader: String, + request_type: OutfitListEvent.RequestType.Type, + action: OutfitListEventAction ) extends PlanetSideGamePacket { type Packet = OutfitListEvent @@ -23,13 +18,144 @@ final case class OutfitListEvent( def encode: Attempt[BitVector] = OutfitListEvent.encode(this) } -object OutfitListEvent extends Marshallable[OutfitListEvent] { - implicit val codec: Codec[OutfitListEvent] = ( - ("outfit_score" | uint32) :: + +abstract class OutfitListEventAction(val code: Int) + +object OutfitListEventAction { + + final case class ListElementOutfit( + outfit_id: Long, + points: Long, + members: Long, + outfit_name: String, + outfit_leader: String, + ) extends OutfitListEventAction(code = 2) + + /* + TODO: Check packet when bundle packet has been implemented (packet containing OutfitListEvent packets back to back) + For now it seems like there is no valid packet captured + */ + final case class Unk3( + unk1: Long, + data: ByteVector + ) extends OutfitListEventAction(code = 3) + + final case class Unknown(badCode: Int, data: BitVector) extends OutfitListEventAction(badCode) + + /** + * The `Codec`s used to transform the input stream into the context of a specific action + * and extract the field data from that stream. + */ + object Codecs { + private val everFailCondition = conditional(included = false, bool) + + val ListElementOutfitCodec: Codec[ListElementOutfit] = ( ("unk1" | uint32L) :: - ("unk2" | uint32L) :: - ("unk3" | uint(3)) :: - ("outfit_name" | PacketHelpers.encodedWideStringAligned(5)) :: - ("outfit_leader" | PacketHelpers.encodedWideString) - ).as[OutfitListEvent] + ("points" | uint32L) :: + ("members" | uint32L) :: + ("outfit_name" | PacketHelpers.encodedWideStringAligned(5)) :: + ("outfit_leader" | PacketHelpers.encodedWideString) + ).xmap[ListElementOutfit]( + { + case u1 :: points :: members :: outfit_name :: outfit_leader :: HNil => + ListElementOutfit(u1, points, members, outfit_name, outfit_leader) + }, + { + case ListElementOutfit(u1, points, members, outfit_name, outfit_leader) => + u1 :: points :: members :: outfit_name :: outfit_leader :: HNil + } + ) + + val Unk3Codec: Codec[Unk3] = ( + ("unk1" | uint32L) :: + ("data" | bytes) + ).xmap[Unk3]( + { + case u1 :: data :: HNil => + Unk3(u1, data) + }, + { + case Unk3(u1, data) => + u1 :: data :: HNil + } + ) + + /** + * A common form for known action code indexes with an unknown purpose and transformation is an "Unknown" object. + * + * @param action the action behavior code + * @return a transformation between the action code and the unknown bit data + */ + def unknownCodec(action: Int): Codec[Unknown] = + bits.xmap[Unknown]( + data => Unknown(action, data), + { + case Unknown(_, data) => data + } + ) + + /** + * The action code was completely unanticipated! + * + * @param action the action behavior code + * @return nothing; always fail + */ + def failureCodec(action: Int): Codec[OutfitListEventAction] = + everFailCondition.exmap[OutfitListEventAction]( + _ => Attempt.failure(Err(s"can not match a codec pattern for decoding $action")), + _ => Attempt.failure(Err(s"can not match a codec pattern for encoding $action")) + ) + } +} + +object OutfitListEvent extends Marshallable[OutfitListEvent] { + import shapeless.{::, HNil} + + object RequestType extends Enumeration { + type Type = Value + + val Unk0: RequestType.Value = Value(0) + val Unk1: RequestType.Value = Value(1) + val ListElementOutfit: RequestType.Value = Value(2) + val Unk3: RequestType.Value = Value(3) + 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, uintL(3)) + } + + private def selectFromType(code: Int): Codec[OutfitListEventAction] = { + import OutfitListEventAction.Codecs._ + import scala.annotation.switch + + ((code: @switch) match { + case 0 => unknownCodec(action = code) + case 1 => unknownCodec(action = code) + case 2 => ListElementOutfitCodec + case 3 => Unk3Codec // indicated in code + case 4 => unknownCodec(action = code) + case 5 => unknownCodec(action = code) + case 6 => unknownCodec(action = code) + case 7 => unknownCodec(action = code) + + case _ => failureCodec(code) + }).asInstanceOf[Codec[OutfitListEventAction]] + } + + implicit val codec: Codec[OutfitListEvent] = ( + ("request_type" | RequestType.codec) >>:~ { request_type => + ("action" | selectFromType(request_type.id)).hlist + } + ).xmap[OutfitListEvent]( + { + case request_type :: action :: HNil => + OutfitListEvent(request_type, action) + }, + { + case OutfitListEvent(request_type, action) => + request_type :: action :: HNil + } + ) } diff --git a/src/main/scala/net/psforever/packet/game/OutfitRequest.scala b/src/main/scala/net/psforever/packet/game/OutfitRequest.scala index 585ed5ee..c62b1fda 100644 --- a/src/main/scala/net/psforever/packet/game/OutfitRequest.scala +++ b/src/main/scala/net/psforever/packet/game/OutfitRequest.scala @@ -149,7 +149,7 @@ object OutfitRequest extends Marshallable[OutfitRequest] { val Rank: RequestType.Value = Value(1) val Unk2: RequestType.Value = Value(2) val Detail: RequestType.Value = Value(3) - val List: RequestType.Value = Value(4) + val List: RequestType.Value = Value(4) // sent by client if menu is either open (true) or closed (false) implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(3)) } @@ -175,7 +175,7 @@ object OutfitRequest extends Marshallable[OutfitRequest] { } ).xmap[OutfitRequest]( { - case _:: id:: action :: HNil => + case _ :: id:: action :: HNil => OutfitRequest(id, action) }, { diff --git a/src/test/scala/game/OutfitEventTest.scala b/src/test/scala/game/OutfitEventTest.scala index eab4f75a..8837b93c 100644 --- a/src/test/scala/game/OutfitEventTest.scala +++ b/src/test/scala/game/OutfitEventTest.scala @@ -58,8 +58,8 @@ class OutfitEventTest extends Specification { action mustEqual Unk0( OutfitInfo( outfit_name = "Black Armored Reapers", - unk6 = 223190045, - unk7 = 223190045, + outfit_points1 = 223190045, + outfit_points2 = 223190045, member_count = 171, unk9 = 0, OutfitRankNames("Dog Meat","Russian","","","Squad Leaders","Acting Commanders","Reapers",""), @@ -87,8 +87,8 @@ class OutfitEventTest extends Specification { Unk0( OutfitInfo( outfit_name = "Black Armored Reapers", - unk6 = 223190045, - unk7 = 223190045, + outfit_points1 = 223190045, + outfit_points2 = 223190045, member_count = 171, unk9 = 0, OutfitRankNames("Dog Meat","Russian","","","Squad Leaders","Acting Commanders","Reapers",""), @@ -145,8 +145,8 @@ class OutfitEventTest extends Specification { outfit_guid mustEqual 2147418113L action mustEqual Unk2(OutfitInfo( outfit_name = "PlanetSide_Forever_Vanu", - unk6 = 0, - unk7 = 0, + outfit_points1 = 0, + outfit_points2 = 0, member_count = 1, unk9 = 0, OutfitRankNames("","","","","","","",""), @@ -173,8 +173,8 @@ class OutfitEventTest extends Specification { Unk2( OutfitInfo( outfit_name = "PlanetSide_Forever_Vanu", - unk6 = 0, - unk7 = 0, + outfit_points1 = 0, + outfit_points2 = 0, member_count = 1, unk9 = 0, OutfitRankNames("","","","","","","",""), diff --git a/src/test/scala/game/OutfitListEventTest.scala b/src/test/scala/game/OutfitListEventTest.scala index 7935df12..e7731a2d 100644 --- a/src/test/scala/game/OutfitListEventTest.scala +++ b/src/test/scala/game/OutfitListEventTest.scala @@ -2,29 +2,30 @@ package game import net.psforever.packet._ -import net.psforever.packet.game._ +import net.psforever.packet.game.OutfitListEvent +import net.psforever.packet.game.OutfitListEvent.RequestType +import net.psforever.packet.game.OutfitListEventAction.ListElementOutfit import org.specs2.mutable._ import scodec.bits.ByteVector class OutfitListEventTest extends Specification { - val unk0_ABC: ByteVector = ByteVector.fromValidHex("98 5e83a000 0000 e180 0080 0000 11404e0069006700680074004c006f00720064007300 854e005900430061007400") - val unk0_DEF: ByteVector = ByteVector.fromValidHex("98 4ec28100 151a 6280 0340 0000 11a0490052004f004e004600490053005400200043006c0061006e00 8654006f006c006a00") - val unk1_ABC: ByteVector = ByteVector.fromValidHex("98 4723c000 02aa 81e0 0220 0000 11006900470061006d00650073002d004500 906900670061006d006500730043005400460057006800610063006b002d004500") - val unk2_ABC: ByteVector = ByteVector.fromValidHex("98 49a3c000 116d a4e0 0040 0000 11a042006c006f006f00640020006f0066002000560061006e007500 864b00610072006e002d004500") - val unk3_ABC: ByteVector = ByteVector.fromValidHex("98 49c3c000 0df5 87c0 0140 0000 11a054006800650020004e00650076006500720068006f006f006400 8e6f00460058006f00530074006f006e0065004d0061006e002d004700") - val unk4_ABC: ByteVector = ByteVector.fromValidHex("98 4c03c000 0240 6040 0060 0000 1220540068006500200042006c00610063006b0020004b006e0069006700680074007300 874400720061007a00760065006e00") - val unk5_ABC: ByteVector = ByteVector.fromValidHex("98 5383c000 14b7 09a0 00c0 0000 10a03e005400760053003c00 89430061007000650062006f00610074007300") - val unk6_ABC: ByteVector = ByteVector.fromValidHex("98 5b03c000 035d 6700 0040 0000 11404c006f0073007400200043006100750073006500 895a00650072006f004b00650077006c006c00") - val unk7_ABC: ByteVector = ByteVector.fromValidHex("98 4043e000 19fb 8261 6140 0000 11e0540068006500200042006c00610063006b00200054006f00770065007200 874b00720075007000680065007800") - val unk8_ABC: ByteVector = ByteVector.fromValidHex("98 4a03e000 17e2") // broken, limit of SMP + val unk0_ABC: ByteVector = ByteVector.fromValidHex("98 5 e83a0000 000e1800 0800000 11404e0069006700680074004c006f00720064007300 854e005900430061007400") + val unk0_DEF: ByteVector = ByteVector.fromValidHex("98 4 ec281001 51a62800 3400000 11a0490052004f004e004600490053005400200043006c0061006e00 8654006f006c006a00") + val unk1_ABC: ByteVector = ByteVector.fromValidHex("98 4 723c0000 2aa81e00 2200000 11006900470061006d00650073002d004500 906900670061006d006500730043005400460057006800610063006b002d004500") + val unk2_ABC: ByteVector = ByteVector.fromValidHex("98 4 9a3c0001 16da4e00 0400000 11a042006c006f006f00640020006f0066002000560061006e007500 864b00610072006e002d004500") + val unk3_ABC: ByteVector = ByteVector.fromValidHex("98 4 9c3c0000 df587c00 1400000 11a054006800650020004e00650076006500720068006f006f006400 8e6f00460058006f00530074006f006e0065004d0061006e002d004700") + val unk4_ABC: ByteVector = ByteVector.fromValidHex("98 4 c03c0000 24060400 0600000 1220540068006500200042006c00610063006b0020004b006e0069006700680074007300 874400720061007a00760065006e00") + val unk5_ABC: ByteVector = ByteVector.fromValidHex("98 5 383c0001 4b709a00 0c00000 10a03e005400760053003c00 89430061007000650062006f00610074007300") + val unk6_ABC: ByteVector = ByteVector.fromValidHex("98 5 b03c0000 35d67000 0400000 11404c006f0073007400200043006100750073006500 895a00650072006f004b00650077006c006c00") + val unk7_ABC: ByteVector = ByteVector.fromValidHex("98 4 043e0001 9fb82616 1400000 11e0540068006500200042006c00610063006b00200054006f00770065007200 874b00720075007000680065007800") "decode unk0_ABC" in { PacketCoding.decodePacket(unk0_ABC).require match { - case OutfitListEvent(outfit_score, unk1, unk2, unk3, outfit_name, outfit_leader) => - outfit_score mustEqual 1585684480L - unk1 mustEqual 2162229248L - unk2 mustEqual 32768 - unk3 mustEqual 0 + case OutfitListEvent(code, ListElementOutfit(unk1, points, members, outfit_name, outfit_leader)) => + code mustEqual OutfitListEvent.RequestType.ListElementOutfit + unk1 mustEqual 7668 + points mustEqual 788224 + members mustEqual 4 outfit_name mustEqual "NightLords" outfit_leader mustEqual "NYCat" case _ => @@ -33,7 +34,7 @@ class OutfitListEventTest extends Specification { } "encode unk0_ABC" in { - val msg = OutfitListEvent(1585684480L, 2162229248L, 32768, 0, "NightLords", "NYCat") + val msg = OutfitListEvent(RequestType.ListElementOutfit, ListElementOutfit(7668, 788224, 4, "NightLords", "NYCat")) val pkt = PacketCoding.encodePacket(msg).require.toByteVector pkt mustEqual unk0_ABC