mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-14 16:04:41 +00:00
OutfitMemberEvent
I failed horribly implementing two type conditionals, please send help
This commit is contained in:
parent
f3eed484af
commit
7528388eb1
2 changed files with 205 additions and 48 deletions
|
|
@ -1,54 +1,152 @@
|
||||||
// Copyright (c) 2025 PSForever
|
// Copyright (c) 2025 PSForever
|
||||||
package net.psforever.packet.game
|
package net.psforever.packet.game
|
||||||
|
|
||||||
|
import net.psforever.packet.GamePacketOpcode.Type
|
||||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||||
import scodec.Codec
|
import scodec.{Attempt, Codec, Err}
|
||||||
import scodec.bits.ByteVector
|
import scodec.bits.BitVector
|
||||||
import scodec.codecs._
|
import scodec.codecs._
|
||||||
import shapeless.{::, HNil}
|
import shapeless.{::, HNil}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
action is unimplemented! if action == 0 only outfit_id and member_id are sent
|
packet_type is unimplemented! if packet_type == 0 only outfit_id and member_id are sent
|
||||||
action2 is unimplemented! if action2 == 0 unk2 will contain one additional uint32L
|
action is unimplemented! if action == 0 unk2 will contain one additional uint32L
|
||||||
unk2 contains one byte of padding. may contain 4byte of unknown data depending on action2
|
unk0_padding contains one byte of padding. may contain 4byte of unknown data depending on action
|
||||||
*/
|
*/
|
||||||
final case class OutfitMemberEvent(
|
final case class OutfitMemberEvent(
|
||||||
action: Int, // action is unimplemented
|
packet_type: Int, // only 0 is known // TODO: needs implementation
|
||||||
outfit_id: Long,
|
outfit_id: Long,
|
||||||
member_id: Long,
|
member_id: Long,
|
||||||
member_name: String,
|
member_name: String, // from here is packet_type == 0 only
|
||||||
rank: Int, // 0-7
|
rank: Int, // 0-7
|
||||||
points: Long, // client divides this by 100
|
points: Long, // client divides this by 100
|
||||||
last_login: Long, // seconds ago from current time, 0 if online
|
last_login: Long, // seconds ago from current time, 0 if online
|
||||||
action2: Int, // this should always be 1, otherwise there will be actual data in unk2!
|
action: OutfitMemberEvent.PacketType.Type, // this should always be 1, otherwise there will be actual data in unk0_padding!
|
||||||
padding: ByteVector, // only contains information if unk1 is 0, 1 byte of padding otherwise
|
unk0_padding: OutfitMemberEventAction // only contains information if action is 0, 1 byte of padding otherwise
|
||||||
) extends PlanetSideGamePacket {
|
) extends PlanetSideGamePacket {
|
||||||
type Packet = OutfitMemberEvent
|
type Packet = OutfitMemberEvent
|
||||||
|
|
||||||
def opcode = GamePacketOpcode.OutfitMemberEvent
|
def opcode: Type = GamePacketOpcode.OutfitMemberEvent
|
||||||
|
|
||||||
def encode = OutfitMemberEvent.encode(this)
|
def encode: Attempt[BitVector] = OutfitMemberEvent.encode(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class OutfitMemberEventAction(val code: Int)
|
||||||
|
object OutfitMemberEventAction {
|
||||||
|
|
||||||
|
final case class Unk0(
|
||||||
|
unk0: Long
|
||||||
|
) extends OutfitMemberEventAction(code = 0)
|
||||||
|
|
||||||
|
final case class Padding(
|
||||||
|
padding: Int
|
||||||
|
) extends OutfitMemberEventAction(code = 1)
|
||||||
|
|
||||||
|
final case class Unknown(badCode: Int, data: BitVector) extends OutfitMemberEventAction(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 UnkNonPaddingCodec: Codec[Unk0] = (
|
||||||
|
("unk0" | uint32L)
|
||||||
|
).xmap[Unk0](
|
||||||
|
{
|
||||||
|
case u0 =>
|
||||||
|
Unk0(u0)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case Unk0(u0) =>
|
||||||
|
u0
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
val PaddingCodec: Codec[Padding] = (
|
||||||
|
("padding" | uint4L)
|
||||||
|
).xmap[Padding](
|
||||||
|
{
|
||||||
|
case padding =>
|
||||||
|
Padding(padding)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
case Padding(padding) =>
|
||||||
|
padding
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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[OutfitMemberEventAction] =
|
||||||
|
everFailCondition.exmap[OutfitMemberEventAction](
|
||||||
|
_ => 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 OutfitMemberEvent extends Marshallable[OutfitMemberEvent] {
|
object OutfitMemberEvent extends Marshallable[OutfitMemberEvent] {
|
||||||
|
|
||||||
|
object PacketType extends Enumeration {
|
||||||
|
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
|
||||||
|
|
||||||
|
implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(1))
|
||||||
|
}
|
||||||
|
|
||||||
|
private def selectFromType(code: Int): Codec[OutfitMemberEventAction] = {
|
||||||
|
import OutfitMemberEventAction.Codecs._
|
||||||
|
import scala.annotation.switch
|
||||||
|
|
||||||
|
((code: @switch) match {
|
||||||
|
case 0 => UnkNonPaddingCodec
|
||||||
|
case 1 => PaddingCodec
|
||||||
|
|
||||||
|
case _ => failureCodec(code)
|
||||||
|
}).asInstanceOf[Codec[OutfitMemberEventAction]]
|
||||||
|
}
|
||||||
|
|
||||||
implicit val codec: Codec[OutfitMemberEvent] = (
|
implicit val codec: Codec[OutfitMemberEvent] = (
|
||||||
("action" | uintL(2)) ::
|
("packet_type" | uintL(2)) :: // this should selectFromType
|
||||||
("outfit_id" | uint32L) ::
|
("outfit_id" | uint32L) ::
|
||||||
("member_id" | uint32L) ::
|
("member_id" | uint32L) ::
|
||||||
("member_name" | PacketHelpers.encodedWideStringAligned(6)) ::
|
("member_name" | PacketHelpers.encodedWideStringAligned(6)) :: // from here is packet_type == 0 only
|
||||||
("rank" | uint(3)) ::
|
("rank" | uint(3)) ::
|
||||||
("points" | uint32L) ::
|
("points" | uint32L) ::
|
||||||
("last_login" | uint32L) ::
|
("last_login" | uint32L) ::
|
||||||
("action2" | uintL(1)) ::
|
(("action" | PacketType.codec) >>:~ { action =>
|
||||||
("padding" | bytes)
|
("action_part" | selectFromType(action.id)).hlist
|
||||||
|
})
|
||||||
).xmap[OutfitMemberEvent](
|
).xmap[OutfitMemberEvent](
|
||||||
{
|
{
|
||||||
case unk00 :: outfit_id :: member_id :: member_name :: rank :: points :: last_login :: u1 :: padding :: HNil =>
|
case packet_type :: outfit_id :: member_id :: member_name :: rank :: points :: last_login :: action :: unk0_padding :: HNil =>
|
||||||
OutfitMemberEvent(unk00, outfit_id, member_id, member_name, rank, points, last_login, u1, padding)
|
OutfitMemberEvent(packet_type, outfit_id, member_id, member_name, rank, points, last_login, action, unk0_padding)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
case OutfitMemberEvent(unk00, outfit_id, member_id, member_name, rank, points, last_login, action2, padding) =>
|
// TODO: remove once implemented
|
||||||
unk00 :: outfit_id :: member_id :: member_name :: rank :: points :: last_login :: action2 :: padding :: HNil
|
// 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
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,56 +2,115 @@
|
||||||
package game
|
package game
|
||||||
|
|
||||||
import net.psforever.packet._
|
import net.psforever.packet._
|
||||||
|
import net.psforever.packet.game.OutfitMemberEvent
|
||||||
import net.psforever.packet.game._
|
import net.psforever.packet.game.OutfitMemberEventAction.Padding
|
||||||
import org.specs2.mutable._
|
import org.specs2.mutable._
|
||||||
import scodec.bits._
|
import scodec.bits._
|
||||||
|
|
||||||
class OutfitMemberEventTest extends Specification {
|
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 3518 4000 1a4e 4100 2 180 450078007000650072007400 8483 07e0 119d bfe0 70" // 0x90048640001030c28022404c0061007a00650072003100390038003200f43a45e00b4c604010
|
||||||
val unk0_ABC_Lazer = hex"90 0 4864 0001 030c 2802 24 0 4c0061007a00650072003100390038003200 f43a 45e0 0b4c 6040 10"
|
val Lazer = hex"90 0 4864 0001 030c 2802 24 0 4c0061007a00650072003100390038003200 f43a 45e0 0b4c 6040 10"
|
||||||
|
val Lazer2 = hex"90 0 4864 0001 030c 2802 24 0 4c0061007a00650072003100390038003200 e6dc 25a0 153e 6040 10"
|
||||||
|
val OpolE = hex"90 0 4864 0003 aad6 280a 14 0 4f0070006f006c004500 c9a1 80e0 0d03 2040 10"
|
||||||
|
val Billy = hex"90 0 4864 0003 a41a 280a 20 0 620069006c006c007900320035003600 935f 6000 186a b040 50"
|
||||||
|
val Virus = hex"90 0 4864 0002 1b64 4c02 28 0 5600690072007500730047006900760065007200 2f89 0080 0000 0000 10"
|
||||||
|
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 OpolE = hex"90 0 4864 0003 aad6 280a 14 0 4f0070006f006c004500 c9a1 80e0 0d03 2040 10"
|
val Unk1 = hex"90 5 40542002 3f61e808 0"
|
||||||
val Billy = hex"90 0 4864 0003 a41a 280a 20 0 620069006c006c007900320035003600 935f 6000 186a b040 50"
|
|
||||||
val Lazer = hex"90 0 4864 0001 030c 2802 24 0 4c0061007a00650072003100390038003200 e6dc 25a0 153e 6040 10"
|
|
||||||
val Virus = hex"90 0 4864 0002 1b64 4c02 28 0 5600690072007500730047006900760065007200 2f89 0080 0000 0000 10"
|
|
||||||
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 Unk0 = hex"90 5 40542002 3f61e808 0"
|
"decode Lazer padding" in {
|
||||||
|
PacketCoding.decodePacket(Lazer).require match {
|
||||||
"decode Unk0 ABC" in {
|
case OutfitMemberEvent(packet_type, outfit_id, member_id, member_name, rank, points, last_login, action, unk0_padding) =>
|
||||||
PacketCoding.decodePacket(unk0_ABC_Lazer).require match {
|
packet_type mustEqual 0
|
||||||
case OutfitMemberEvent(action, outfit_id, member_id, member_name, rank, points, last_login, action2, padding) =>
|
outfit_id mustEqual 6418
|
||||||
action mustEqual 0
|
member_id mustEqual 705344
|
||||||
outfit_id mustEqual 6418L
|
member_name mustEqual "Lazer1982"
|
||||||
member_id mustEqual 705344
|
rank mustEqual 7
|
||||||
member_name mustEqual "Lazer1982"
|
points mustEqual 3134113
|
||||||
rank mustEqual 7
|
last_login mustEqual 156506
|
||||||
points mustEqual 3134113
|
action mustEqual OutfitMemberEvent.PacketType.Padding
|
||||||
last_login mustEqual 156506
|
unk0_padding mustEqual Padding(0)
|
||||||
action2 mustEqual 1
|
|
||||||
padding mustEqual ByteVector(0x0)
|
|
||||||
case _ =>
|
case _ =>
|
||||||
ko
|
ko
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"encode Unk0 ABC" in {
|
"encode Lazer padding" in {
|
||||||
val msg = OutfitMemberEvent(
|
val msg = OutfitMemberEvent(
|
||||||
action = 0,
|
packet_type = 0,
|
||||||
outfit_id = 6418L,
|
outfit_id = 6418,
|
||||||
member_id = 705344,
|
member_id = 705344,
|
||||||
member_name = "Lazer1982",
|
member_name = "Lazer1982",
|
||||||
rank = 7,
|
rank = 7,
|
||||||
points = 3134113,
|
points = 3134113,
|
||||||
last_login = 156506,
|
last_login = 156506,
|
||||||
action2 = 1,
|
action = OutfitMemberEvent.PacketType.Padding,
|
||||||
ByteVector.empty
|
unk0_padding = Padding(0)
|
||||||
)
|
)
|
||||||
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
pkt mustEqual unk0_ABC_Lazer
|
pkt mustEqual Lazer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"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
|
||||||
|
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)
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode OpolE padding" in {
|
||||||
|
val msg = OutfitMemberEvent(
|
||||||
|
packet_type = 0,
|
||||||
|
outfit_id = 6418,
|
||||||
|
member_id = 42644970,
|
||||||
|
member_name = "OpolE",
|
||||||
|
rank = 6,
|
||||||
|
points = 461901,
|
||||||
|
last_login = 137576,
|
||||||
|
action = OutfitMemberEvent.PacketType.Padding,
|
||||||
|
unk0_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
|
||||||
|
case _ =>
|
||||||
|
ko
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
"encode Unk0" in {
|
||||||
|
val msg = OutfitMemberEvent(
|
||||||
|
packet_type = 1,
|
||||||
|
outfit_id = 6418,
|
||||||
|
member_id = 42644970,
|
||||||
|
)
|
||||||
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
||||||
|
|
||||||
|
pkt mustEqual Unk1
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue