OutfitListEvent ListElementOutfit decoded

This commit is contained in:
Resaec 2025-08-20 00:03:17 +02:00
parent 308ea20dee
commit d450a1b6e5
5 changed files with 174 additions and 72 deletions

View file

@ -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))
}

View file

@ -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
}
)
}

View file

@ -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)
},
{

View file

@ -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("","","","","","","",""),

View file

@ -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