OutfitMembershipRequest all packets known!

OutfitMembershipResponse decoded, needs rework
OutfitMemberEvent decoded, needs rework
Tests reworked
This commit is contained in:
Resaec 2025-08-21 02:23:32 +02:00
parent b070834a8a
commit 17682c08d6
9 changed files with 393 additions and 287 deletions

View file

@ -3,7 +3,6 @@ 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._

View file

@ -3,42 +3,52 @@ package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import scodec.Codec
import scodec.bits.ByteVector
import scodec.codecs._
import shapeless.{::, HNil}
/*
action is unimplemented! if action == 0 only outfit_id and member_id are sent
action2 is unimplemented! if action2 == 0 unk2 will contain one additional uint32L
unk2 contains one byte of padding. may contain 4byte of unknown data depending on action2
*/
final case class OutfitMemberEvent(
unk00: Int,
outfit_id: Long,
member_id: Long,
member_name: String,
rank: Int, // 0-7
points: Long, // client divides this by 100
last_login: Long, // seconds ago from current time, 0 if online
unk1: Int,
) extends PlanetSideGamePacket {
action: Int, // action is unimplemented
outfit_id: Long,
member_id: Long,
member_name: String,
rank: Int, // 0-7
points: Long, // client divides this by 100
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!
padding: ByteVector, // only contains information if unk1 is 0, 1 byte of padding otherwise
) 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" | uint32L) ::
("member_id" | uint32L) ::
("member_name" | PacketHelpers.encodedWideStringAligned(6)) ::
("rank" | uint(3)) ::
("points" | uint32L) ::
("last_login" | uint32L) ::
("unk1" | uint(5))
("action" | uintL(2)) ::
("outfit_id" | uint32L) ::
("member_id" | uint32L) ::
("member_name" | PacketHelpers.encodedWideStringAligned(6)) ::
("rank" | uint(3)) ::
("points" | uint32L) ::
("last_login" | uint32L) ::
("action2" | uintL(1)) ::
("padding" | bytes)
).xmap[OutfitMemberEvent](
{
case unk00 :: outfit_id :: member_id :: member_name :: rank :: points :: last_login :: u1 :: HNil =>
OutfitMemberEvent(unk00, outfit_id, member_id, member_name, rank, points, last_login, u1)
case unk00 :: outfit_id :: member_id :: member_name :: rank :: points :: last_login :: u1 :: padding :: HNil =>
OutfitMemberEvent(unk00, outfit_id, member_id, member_name, rank, points, last_login, u1, padding)
},
{
case OutfitMemberEvent(unk00, outfit_id, member_id, member_name, rank, points, last_login, u1) =>
unk00 :: outfit_id :: member_id :: member_name :: rank :: points :: last_login :: u1 :: HNil
case OutfitMemberEvent(unk00, outfit_id, member_id, member_name, rank, points, last_login, action2, padding) =>
unk00 :: outfit_id :: member_id :: member_name :: rank :: points :: last_login :: action2 :: padding :: HNil
}
)
}

View file

@ -2,7 +2,6 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.PlanetSideGUID
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}

View file

@ -22,41 +22,50 @@ final case class OutfitMembershipRequest(
abstract class OutfitMembershipRequestAction(val code: Int)
/*
Codecs 2,5,6,7 can either work off of the avatar_id (if GUI was used) or member_name (if chat command was used)
*/
object OutfitMembershipRequestAction {
final case class CreateOutfit(
unk2: String,
unk3: Int,
unk4: Boolean,
final case class Create(
unk1: String,
outfit_name: String
) extends OutfitMembershipRequestAction(code = 0)
final case class FormOutfit(
unk2: String,
unk3: Int,
unk4: Boolean,
final case class Form(
unk1: String,
outfit_name: String
) extends OutfitMembershipRequestAction(code = 1)
final case class Unk2(
unk2: Int,
unk3: Int,
final case class Invite(
avatar_id: Long,
member_name: String,
) extends OutfitMembershipRequestAction(code = 2)
final case class AcceptOutfitInvite(
unk2: String
final case class AcceptInvite(
member_name: String
) extends OutfitMembershipRequestAction(code = 3)
final case class RejectOutfitInvite(
unk2: String
final case class RejectInvite(
member_name: String
) extends OutfitMembershipRequestAction(code = 4)
final case class CancelOutfitInvite(
unk5: Int,
unk6: Int,
outfit_name: String
final case class CancelInvite(
avatar_id: Long,
member_name: String,
) extends OutfitMembershipRequestAction(code = 5)
final case class Kick(
avatar_id: Long,
member_name: String,
) extends OutfitMembershipRequestAction(code = 6)
final case class SetRank(
avatar_id: Long, // 32
rank: Int, // 3
member_name: String,
) extends OutfitMembershipRequestAction(code = 7)
final case class Unknown(badCode: Int, data: BitVector) extends OutfitMembershipRequestAction(badCode)
/**
@ -66,96 +75,122 @@ object OutfitMembershipRequestAction {
object Codecs {
private val everFailCondition = conditional(included = false, bool)
val CreateOutfitCodec: Codec[CreateOutfit] =
val CreateCodec: Codec[Create] =
(
PacketHelpers.encodedWideString ::
uint4L ::
bool ::
PacketHelpers.encodedWideStringAligned(5) ::
PacketHelpers.encodedWideString
).xmap[CreateOutfit](
).xmap[Create](
{
case unk2 :: unk3 :: unk4 :: outfit_name :: HNil =>
CreateOutfit(unk2, unk3, unk4, outfit_name)
case u1 :: outfit_name :: HNil =>
Create(u1, outfit_name)
},
{
case CreateOutfit(unk2, unk3, unk4, outfit_name) =>
unk2 :: unk3 :: unk4 :: outfit_name :: HNil
case Create(u1, outfit_name) =>
u1 :: outfit_name :: HNil
}
)
val FormOutfitCodec: Codec[FormOutfit] =
val FormCodec: Codec[Form] =
(
PacketHelpers.encodedWideString ::
uint4L ::
bool ::
PacketHelpers.encodedWideStringAligned(5) ::
PacketHelpers.encodedWideString
).xmap[FormOutfit](
).xmap[Form](
{
case unk2 :: unk3 :: unk4 :: outfit_name :: HNil =>
FormOutfit(unk2, unk3, unk4, outfit_name)
case u1 :: outfit_name :: HNil =>
Form(u1, outfit_name)
},
{
case FormOutfit(unk2, unk3, unk4, outfit_name) =>
unk2 :: unk3 :: unk4 :: outfit_name :: HNil
case Form(u1, outfit_name) =>
u1 :: outfit_name :: HNil
}
)
val Unk2Codec: Codec[Unk2] =
val InviteCodec: Codec[Invite] =
(
uint16L ::
uint16L ::
uint32L ::
PacketHelpers.encodedWideStringAligned(5)
).xmap[Unk2](
).xmap[Invite](
{
case unk2 :: unk3 :: member_name :: HNil =>
Unk2(unk2, unk3, member_name)
case u1 :: member_name :: HNil =>
Invite(u1, member_name)
},
{
case Unk2(unk2, unk3, member_name) =>
unk2 :: unk3 :: member_name :: HNil
case Invite(u1, member_name) =>
u1 :: member_name :: HNil
}
)
val AcceptOutfitCodec: Codec[AcceptOutfitInvite] =
PacketHelpers.encodedWideString.xmap[AcceptOutfitInvite](
val AcceptInviteCodec: Codec[AcceptInvite] =
PacketHelpers.encodedWideString.xmap[AcceptInvite](
{
case unk2 =>
AcceptOutfitInvite(unk2)
case u1 =>
AcceptInvite(u1)
},
{
case AcceptOutfitInvite(unk2) =>
unk2
case AcceptInvite(u1) =>
u1
}
)
val RejectOutfitCodec: Codec[RejectOutfitInvite] =
PacketHelpers.encodedWideString.xmap[RejectOutfitInvite](
val RejectInviteCodec: Codec[RejectInvite] =
PacketHelpers.encodedWideString.xmap[RejectInvite](
{
case unk2 =>
RejectOutfitInvite(unk2)
case u1 =>
RejectInvite(u1)
},
{
case RejectOutfitInvite(unk2) =>
unk2
case RejectInvite(u1) =>
u1
}
)
val CancelOutfitCodec: Codec[CancelOutfitInvite] =
val CancelInviteCodec: Codec[CancelInvite] =
(
uint16L ::
uint16L ::
uint32L ::
PacketHelpers.encodedWideStringAligned(5)
).xmap[CancelOutfitInvite](
).xmap[CancelInvite](
{
case unk5 :: unk6 :: outfit_name :: HNil =>
CancelOutfitInvite(unk5, unk6, outfit_name)
case u1 :: outfit_name :: HNil =>
CancelInvite(u1, outfit_name)
},
{
case CancelOutfitInvite(unk5, unk6, outfit_name) =>
unk5 :: unk6 :: outfit_name :: HNil
case CancelInvite(u1, outfit_name) =>
u1 :: outfit_name :: HNil
}
)
val KickCodec: Codec[Kick] =
(
uint32L ::
PacketHelpers.encodedWideStringAligned(5)
).xmap[Kick](
{
case u1 :: member_name :: HNil =>
Kick(u1, member_name)
},
{
case Kick(u1, member_name) =>
u1 :: member_name :: HNil
}
)
val SetRankCodec: Codec[SetRank] =
(
uint32L ::
uintL(3) ::
PacketHelpers.encodedWideStringAligned(2)
).xmap[SetRank](
{
case u1 :: rank :: member_name :: HNil =>
SetRank(u1, rank, member_name)
},
{
case SetRank(u1, rank, member_name) =>
u1 :: rank :: member_name :: HNil
}
)
/**
* A common form for known action code indexes with an unknown purpose and transformation is an "Unknown" object.
*
@ -191,12 +226,12 @@ object OutfitMembershipRequest extends Marshallable[OutfitMembershipRequest] {
val Create: RequestType.Value = Value(0)
val Form: RequestType.Value = Value(1)
val Unk2: RequestType.Value = Value(2)
val Invite: RequestType.Value = Value(2)
val Accept: RequestType.Value = Value(3)
val Reject: RequestType.Value = Value(4)
val Cancel: RequestType.Value = Value(5)
val Unk6: RequestType.Value = Value(6) // 6 and 7 seen as failed decodes, validity unknown
val Unk7: RequestType.Value = Value(7)
val Kick: RequestType.Value = Value(6)
val SetRank: RequestType.Value = Value(7)
implicit val codec: Codec[Type] = PacketHelpers.createEnumerationCodec(this, uintL(3))
}
@ -206,15 +241,15 @@ object OutfitMembershipRequest extends Marshallable[OutfitMembershipRequest] {
import scala.annotation.switch
((code: @switch) match {
case 0 => CreateOutfitCodec
case 1 => FormOutfitCodec // so far same as Create
case 2 => Unk2Codec
case 3 => AcceptOutfitCodec
case 4 => RejectOutfitCodec // so far same as Accept
case 5 => CancelOutfitCodec
case 6 => unknownCodec(action = code)
case 7 => unknownCodec(action = code)
// 3 bit limit
case 0 => CreateCodec
case 1 => FormCodec // so far same as Create
case 2 => InviteCodec
case 3 => AcceptInviteCodec
case 4 => RejectInviteCodec
case 5 => CancelInviteCodec
case 6 => KickCodec
case 7 => SetRankCodec
case _ => failureCodec(code)
}).asInstanceOf[Codec[OutfitMembershipRequestAction]]
}

View file

@ -3,7 +3,6 @@ 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._
@ -12,10 +11,9 @@ import shapeless.{::, HNil}
final case class OutfitMembershipResponse(
response_type: OutfitMembershipResponse.ResponseType.Type,
unk0: Int,
unk1: Int,
outfit_id: Long,
target_guid: PlanetSideGUID,
unk3: Int,
//unk4: Boolean,
target_id: Long,
action: OutfitMembershipResponseAction
) extends PlanetSideGamePacket {
type Packet = OutfitMembershipResponse
@ -28,21 +26,39 @@ final case class OutfitMembershipResponse(
abstract class OutfitMembershipResponseAction(val code: Int)
object OutfitMembershipResponseAction {
final case class CreateOutfitResponse(str1: String, str2: String, str3: String) extends OutfitMembershipResponseAction(code = 0)
final case class Universal(
str1: String,
str2: String,
flag: Boolean
) extends OutfitMembershipResponseAction(-1)
final case class Unk1OutfitResponse(player_name: String, outfit_name: String, unk7: Int) extends OutfitMembershipResponseAction(code = 1)
final case class CreateResponse(
str1: String,
str2: String,
str3: String
) extends OutfitMembershipResponseAction(code = 0)
final case class Unk2OutfitResponse(player_name: String, outfit_name: String, unk7: Int) extends OutfitMembershipResponseAction(code = 2) // unk7 = rank?
final case class Unk1OutfitResponse(
player_name: String,
outfit_name: String,
unk7: Int
) extends OutfitMembershipResponseAction(code = 1)
final case class Unk3OutfitResponse(unk2: String) extends OutfitMembershipResponseAction(code = 3)
final case class Unk2OutfitResponse(
player_name: String,
outfit_name: String,
unk7: Int
) extends OutfitMembershipResponseAction(code = 2) // unk7 = rank?
final case class Unk4OutfitResponse(unk5: Int, unk6: Int, outfit_name: String) extends OutfitMembershipResponseAction(code = 4)
final case class Unk3OutfitResponse(
unk2: String
) extends OutfitMembershipResponseAction(code = 3)
final case class Unk5OutfitResponse() extends OutfitMembershipResponseAction(code = 5)
final case class Unk6OutfitResponse() extends OutfitMembershipResponseAction(code = 6)
final case class Unk7OutfitResponse() extends OutfitMembershipResponseAction(code = 7)
final case class Unk4OutfitResponse(
unk5: Int,
unk6: Int,
outfit_name: String
) extends OutfitMembershipResponseAction(code = 4)
final case class Unknown(badCode: Int, data: BitVector) extends OutfitMembershipResponseAction(badCode)
@ -53,17 +69,32 @@ object OutfitMembershipResponseAction {
object Codecs {
private val everFailCondition = conditional(included = false, bool)
val Unk0OutfitCodec: Codec[CreateOutfitResponse] = (
val UniversalResponseCodec: Codec[OutfitMembershipResponseAction] = (
PacketHelpers.encodedWideStringAligned(5) ::
PacketHelpers.encodedWideString ::
("flag" | bool)
).xmap[OutfitMembershipResponseAction](
{
case str1 :: str2 :: flag :: HNil =>
Universal(str1, str2, flag)
},
{
case Universal(str1, str2, flag) =>
str1 :: str2 :: flag :: HNil
}
)
val CreateOutfitCodec: Codec[CreateResponse] = (
PacketHelpers.encodedWideStringAligned(5) ::
PacketHelpers.encodedWideString ::
PacketHelpers.encodedWideString
).xmap[CreateOutfitResponse](
).xmap[CreateResponse](
{
case str1 :: str2 :: str3 :: HNil =>
CreateOutfitResponse(str1, str2, str3)
CreateResponse(str1, str2, str3)
},
{
case CreateOutfitResponse(str1, str2, str3) =>
case CreateResponse(str1, str2, str3) =>
str1 :: str2 :: str3 :: HNil
}
)
@ -99,7 +130,7 @@ object OutfitMembershipResponseAction {
)
val Unk3OutfitCodec: Codec[Unk3OutfitResponse] =
PacketHelpers.encodedWideString.xmap[Unk3OutfitResponse](
PacketHelpers.encodedWideStringAligned(5).xmap[Unk3OutfitResponse](
{
case unk2 =>
Unk3OutfitResponse(unk2)
@ -110,8 +141,11 @@ object OutfitMembershipResponseAction {
}
)
val Unk4OutfitCodec: Codec[Unk4OutfitResponse] =
(uint16L :: uint16L :: PacketHelpers.encodedWideStringAligned(5)).xmap[Unk4OutfitResponse](
val Unk4OutfitCodec: Codec[Unk4OutfitResponse] = (
uint16L ::
uint16L ::
PacketHelpers.encodedWideStringAligned(5)
).xmap[Unk4OutfitResponse](
{
case unk5 :: unk6 :: outfit_name :: HNil =>
Unk4OutfitResponse(unk5, unk6, outfit_name)
@ -122,42 +156,6 @@ object OutfitMembershipResponseAction {
}
)
// val Unk5OutfitCodec: Codec[Unk5OutfitResponse] =
// (uint16L :: uint16L :: PacketHelpers.encodedWideStringAligned(5)).xmap[Unk5OutfitResponse](
// {
// case unk5 :: unk6 :: outfit_name :: HNil =>
// Unk5OutfitResponse(unk5, unk6, outfit_name)
// },
// {
// case Unk5OutfitResponse(unk5, unk6, outfit_name) =>
// unk5 :: unk6 :: outfit_name :: HNil
// }
// )
//
// val Unk6OutfitCodec: Codec[Unk6OutfitResponse] =
// (uint16L :: uint16L :: PacketHelpers.encodedWideStringAligned(5)).xmap[Unk6OutfitResponse](
// {
// case _ =>
// Unk6OutfitResponse()
// },
// {
// case Unk6OutfitResponse() =>
// _
// }
// )
//
// val Unk7OutfitCodec: Codec[Unk7OutfitResponse] =
// (uint16L :: uint16L :: PacketHelpers.encodedWideStringAligned(5)).xmap[Unk7OutfitResponse](
// {
// case _ =>
// Unk7OutfitResponse()
// },
// {
// case Unk7OutfitResponse() =>
// _
// }
// )
/**
* A common form for known action code indexes with an unknown purpose and transformation is an "Unknown" object.
* @param action the action behavior code
@ -206,14 +204,24 @@ object OutfitMembershipResponse extends Marshallable[OutfitMembershipResponse] {
import scala.annotation.switch
((code: @switch) match {
case 0 => Unk0OutfitCodec // seem as OMReq Create response
case 1 => Unk1OutfitCodec
case 2 => Unk2OutfitCodec
case 3 => Unk3OutfitCodec
case 4 => Unk4OutfitCodec
case 5 => unknownCodec(action = code)
case 6 => unknownCodec(action = code)
case 7 => unknownCodec(action = code)
case 0 => UniversalResponseCodec
case 1 => UniversalResponseCodec
case 2 => UniversalResponseCodec
case 3 => UniversalResponseCodec
case 4 => UniversalResponseCodec
case 5 => UniversalResponseCodec
case 6 => UniversalResponseCodec
case 7 => UniversalResponseCodec
// case 0 => CreateOutfitCodec // seem as OMReq Create response
// case 1 => Unk1OutfitCodec
// case 2 => Unk2OutfitCodec
// case 3 => Unk3OutfitCodec
// case 4 => Unk4OutfitCodec
// case 5 => unknownCodec(action = code)
// case 6 => unknownCodec(action = code)
// case 7 => unknownCodec(action = code)
// 3 bit limit
case _ => failureCodec(code)
}).asInstanceOf[Codec[OutfitMembershipResponseAction]]
@ -221,21 +229,20 @@ object OutfitMembershipResponse extends Marshallable[OutfitMembershipResponse] {
implicit val codec: Codec[OutfitMembershipResponse] = (
("response_type" | ResponseType.codec) >>:~ { response_type =>
("unk0" | uint8L) ::
("unk0" | uintL(5)) ::
("unk1" | uintL(3)) ::
("outfit_id" | uint32L) ::
("target_guid" | PlanetSideGUID.codec) ::
("unk3" | uint16L) ::
//("unk4" | bool) ::
("action" | selectFromType(response_type.id))
("target_id" | uint32L) ::
("action" | selectFromType(response_type.id))
}
).xmap[OutfitMembershipResponse](
{
case response_type :: u0 :: outfit_id :: target_guid :: u3 :: action :: HNil =>
OutfitMembershipResponse(response_type, u0, outfit_id, target_guid, u3, action)
case response_type :: u0 :: u1 :: outfit_id :: target_id :: action :: HNil =>
OutfitMembershipResponse(response_type, u0, u1, outfit_id, target_id, action)
},
{
case OutfitMembershipResponse(response_type, u0, outfit_id, u2, u3, action) =>
response_type :: u0 :: outfit_id :: u2 :: u3 :: action :: HNil
case OutfitMembershipResponse(response_type, u0, u1, outfit_id, target_id, action) =>
response_type :: u0 :: u1 :: outfit_id :: target_id :: action :: HNil
}
)
}