OutfitMemberEvent now supports the two main packet types Unk0 and Unk1.

Support for Unk0's subtypes Unk0 and Padding have been removed in favour of the main type.
Should be reimplemented at some point, but I don't know how yet.
This commit is contained in:
Resaec 2025-08-23 21:54:07 +02:00
parent 7528388eb1
commit e3fe9b69bf
2 changed files with 94 additions and 97 deletions

View file

@ -8,21 +8,11 @@ import scodec.bits.BitVector
import scodec.codecs._
import shapeless.{::, HNil}
/*
packet_type is unimplemented! if packet_type == 0 only outfit_id and member_id are sent
action is unimplemented! if action == 0 unk2 will contain one additional uint32L
unk0_padding contains one byte of padding. may contain 4byte of unknown data depending on action
*/
final case class OutfitMemberEvent(
packet_type: Int, // only 0 is known // TODO: needs implementation
packet_type: OutfitMemberEvent.PacketType.Type,
outfit_id: Long,
member_id: Long,
member_name: String, // from here is packet_type == 0 only
rank: Int, // 0-7
points: Long, // client divides this by 100
last_login: Long, // seconds ago from current time, 0 if online
action: OutfitMemberEvent.PacketType.Type, // this should always be 1, otherwise there will be actual data in unk0_padding!
unk0_padding: OutfitMemberEventAction // only contains information if action is 0, 1 byte of padding otherwise
action: OutfitMemberEventAction
) extends PlanetSideGamePacket {
type Packet = OutfitMemberEvent
@ -34,12 +24,30 @@ final case class OutfitMemberEvent(
abstract class OutfitMemberEventAction(val code: Int)
object OutfitMemberEventAction {
object PacketType extends Enumeration {
type Type = Value
val Unk0: PacketType.Value = Value(0)
val Padding: PacketType.Value = Value(1)
implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(1))
}
/*
action is unimplemented! if action == 0 unk2 will contain one additional uint32L
unk0_padding contains one byte of padding. may contain 4byte of unknown data depending on action
*/
final case class Unk0(
unk0: Long
member_name: String,
rank: Int,
points: Long, // client divides this by 100
last_login: Long, // seconds ago from current time, 0 if online
action: PacketType.Type, // should always be 1, otherwise there will be actual data in padding. not implemented!
padding: Int // should always be 0, 4 bits of padding // only contains data if action is 0
) extends OutfitMemberEventAction(code = 0)
final case class Padding(
padding: Int
final case class Unk1(
) extends OutfitMemberEventAction(code = 1)
final case class Unknown(badCode: Int, data: BitVector) extends OutfitMemberEventAction(badCode)
@ -51,31 +59,25 @@ object OutfitMemberEventAction {
object Codecs {
private val everFailCondition = conditional(included = false, bool)
val UnkNonPaddingCodec: Codec[Unk0] = (
("unk0" | uint32L)
val Unk0Codec: Codec[Unk0] = (
("member_name" | PacketHelpers.encodedWideStringAligned(6)) :: // from here is packet_type == 0 only
("rank" | uint(3)) ::
("points" | uint32L) ::
("last_login" | uint32L) ::
("action" | OutfitMemberEventAction.PacketType.codec) ::
("padding" | uint4L)
).xmap[Unk0](
{
case u0 =>
Unk0(u0)
case member_name :: rank :: points :: last_login :: action :: padding :: HNil =>
Unk0(member_name, rank, points, last_login, action, padding)
},
{
case Unk0(u0) =>
u0
case Unk0(member_name, rank, points, last_login, action, padding) =>
member_name :: rank :: points :: last_login :: action :: padding :: HNil
}
)
val PaddingCodec: Codec[Padding] = (
("padding" | uint4L)
).xmap[Padding](
{
case padding =>
Padding(padding)
},
{
case Padding(padding) =>
padding
}
)
val Unk1Codec: Codec[Unk1] = PacketHelpers.emptyCodec(Unk1())
/**
* A common form for known action code indexes with an unknown purpose and transformation is an "Unknown" object.
@ -109,9 +111,9 @@ object OutfitMemberEvent extends Marshallable[OutfitMemberEvent] {
type Type = Value
val Unk0: PacketType.Value = Value(0)
val Padding: PacketType.Value = Value(1) // Info: Player has been invited / response to OutfitMembershipRequest Unk2 for that player
val Unk1: PacketType.Value = Value(1) // Info: Player has been invited / response to OutfitMembershipRequest Unk2 for that player
implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(1))
implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(2))
}
private def selectFromType(code: Int): Codec[OutfitMemberEventAction] = {
@ -119,34 +121,27 @@ object OutfitMemberEvent extends Marshallable[OutfitMemberEvent] {
import scala.annotation.switch
((code: @switch) match {
case 0 => UnkNonPaddingCodec
case 1 => PaddingCodec
case 0 => Unk0Codec
case 1 => Unk1Codec
case _ => failureCodec(code)
}).asInstanceOf[Codec[OutfitMemberEventAction]]
}
implicit val codec: Codec[OutfitMemberEvent] = (
("packet_type" | uintL(2)) :: // this should selectFromType
("packet_type" | PacketType.codec) >>:~ { packet_type =>
("outfit_id" | uint32L) ::
("member_id" | uint32L) ::
("member_name" | PacketHelpers.encodedWideStringAligned(6)) :: // from here is packet_type == 0 only
("rank" | uint(3)) ::
("points" | uint32L) ::
("last_login" | uint32L) ::
(("action" | PacketType.codec) >>:~ { action =>
("action_part" | selectFromType(action.id)).hlist
})
("action" | selectFromType(packet_type.id)).hlist
}
).xmap[OutfitMemberEvent](
{
case packet_type :: outfit_id :: member_id :: member_name :: rank :: points :: last_login :: action :: unk0_padding :: HNil =>
OutfitMemberEvent(packet_type, outfit_id, member_id, member_name, rank, points, last_login, action, unk0_padding)
case packet_type :: outfit_id :: member_id:: action :: HNil =>
OutfitMemberEvent(packet_type, outfit_id, member_id, action)
},
{
// TODO: remove once implemented
// ensure we send packet_type 0 only
case OutfitMemberEvent(_, outfit_id, member_id, member_name, rank, points, last_login, action, unk0_padding) =>
0 :: outfit_id :: member_id :: member_name :: rank :: points :: last_login :: action :: unk0_padding :: HNil
case OutfitMemberEvent(packet_type, outfit_id, member_id, action) =>
packet_type :: outfit_id :: member_id :: action :: HNil
}
)
}

View file

@ -2,8 +2,8 @@
package game
import net.psforever.packet._
import net.psforever.packet.game.OutfitMemberEvent
import net.psforever.packet.game.OutfitMemberEventAction.Padding
import net.psforever.packet.game.{OutfitMemberEvent, OutfitMemberEventAction}
import net.psforever.packet.game.OutfitMemberEventAction._
import org.specs2.mutable._
import scodec.bits._
@ -18,20 +18,20 @@ class OutfitMemberEventTest extends Specification {
val PvtPa = hex"90 0 4864 0000 1e69 e80a 2c 0 500076007400500061006e00630061006b0065007300 705e a080 0a85 e060 10"
val Night = hex"90 0 4864 0002 4cf0 3802 28 0 4e006900670068007400770069006e0067003100 b8fb 9a40 0da6 ec80 50"
val Unk1 = hex"90 5 40542002 3f61e808 0"
val unk1 = hex"90 5 40542002 3f61e808 0"
"decode Lazer padding" in {
PacketCoding.decodePacket(Lazer).require match {
case OutfitMemberEvent(packet_type, outfit_id, member_id, member_name, rank, points, last_login, action, unk0_padding) =>
packet_type mustEqual 0
outfit_id mustEqual 6418
member_id mustEqual 705344
member_name mustEqual "Lazer1982"
rank mustEqual 7
points mustEqual 3134113
last_login mustEqual 156506
action mustEqual OutfitMemberEvent.PacketType.Padding
unk0_padding mustEqual Padding(0)
case OutfitMemberEvent(packet_type, outfit_id, member_id, Unk0(member_name, rank, points, last_login, action, padding)) =>
packet_type mustEqual OutfitMemberEvent.PacketType.Unk0
outfit_id mustEqual 6418
member_id mustEqual 705344
member_name mustEqual "Lazer1982"
rank mustEqual 7
points mustEqual 3134113
last_login mustEqual 156506
action mustEqual OutfitMemberEventAction.PacketType.Padding
padding mustEqual 0
case _ =>
ko
}
@ -39,15 +39,17 @@ class OutfitMemberEventTest extends Specification {
"encode Lazer padding" in {
val msg = OutfitMemberEvent(
packet_type = 0,
packet_type = OutfitMemberEvent.PacketType.Unk0,
outfit_id = 6418,
member_id = 705344,
member_name = "Lazer1982",
rank = 7,
points = 3134113,
last_login = 156506,
action = OutfitMemberEvent.PacketType.Padding,
unk0_padding = Padding(0)
Unk0(
member_name = "Lazer1982",
rank = 7,
points = 3134113,
last_login = 156506,
action = OutfitMemberEventAction.PacketType.Padding,
padding = 0
)
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
@ -56,16 +58,16 @@ class OutfitMemberEventTest extends Specification {
"decode OpolE padding" in {
PacketCoding.decodePacket(OpolE).require match {
case OutfitMemberEvent(packet_type, outfit_id, member_id, member_name, rank, points, last_login, action, unk0_padding) =>
packet_type mustEqual 0
case OutfitMemberEvent(packet_type, outfit_id, member_id, Unk0(member_name, rank, points, last_login, action, unk0_padding)) =>
packet_type mustEqual OutfitMemberEvent.PacketType.Unk0
outfit_id mustEqual 6418
member_id mustEqual 42644970
member_name mustEqual "OpolE"
rank mustEqual 6
points mustEqual 461901
last_login mustEqual 137576
action mustEqual OutfitMemberEvent.PacketType.Padding
unk0_padding mustEqual Padding(0)
action mustEqual OutfitMemberEventAction.PacketType.Padding
unk0_padding mustEqual 0
case _ =>
ko
}
@ -73,44 +75,44 @@ class OutfitMemberEventTest extends Specification {
"encode OpolE padding" in {
val msg = OutfitMemberEvent(
packet_type = 0,
packet_type = OutfitMemberEvent.PacketType.Unk0,
outfit_id = 6418,
member_id = 42644970,
member_name = "OpolE",
rank = 6,
points = 461901,
last_login = 137576,
action = OutfitMemberEvent.PacketType.Padding,
unk0_padding = Padding(0)
Unk0(
member_name = "OpolE",
rank = 6,
points = 461901,
last_login = 137576,
action = OutfitMemberEventAction.PacketType.Padding,
padding = 0
)
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual OpolE
}
// TODO: these are broken because the decoder can only handle packet_type 0
/*
"decode Unk0" in {
PacketCoding.decodePacket(Unk1).require match {
case OutfitMemberEvent(packet_type, outfit_id, member_id) =>
packet_type mustEqual 1
outfit_id mustEqual 6418
member_id mustEqual 42644970
"decode Unk1" in {
PacketCoding.decodePacket(unk1).require match {
case OutfitMemberEvent(packet_type,outfit_id, member_id, Unk1()) =>
packet_type mustEqual OutfitMemberEvent.PacketType.Unk1
outfit_id mustEqual 529744
member_id mustEqual 41605263
case _ =>
ko
}
}
"encode Unk0" in {
"encode Unk1" in {
val msg = OutfitMemberEvent(
packet_type = 1,
outfit_id = 6418,
member_id = 42644970,
packet_type = OutfitMemberEvent.PacketType.Unk1,
outfit_id = 529744,
member_id = 41605263,
Unk1()
)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual Unk1
pkt mustEqual unk1
}
*/
}