From 607fb82254d6108ed8217d283300f96535a32e53 Mon Sep 17 00:00:00 2001 From: Resaec Date: Sat, 20 Dec 2025 01:55:56 +0100 Subject: [PATCH] Added decoder for SquadBindInfoMessage and SquadFacilityBindInfoMessage Added ExperienceType 1 (unk) --- .../psforever/packet/GamePacketOpcode.scala | 4 +- .../packet/game/SquadBindInfoMessage.scala | 39 +++++ .../game/SquadFacilityBindInfoMessage.scala | 27 +++ .../net/psforever/types/ExperienceType.scala | 3 +- .../scala/game/SquadBindInfoMessageTest.scala | 160 ++++++++++++++++++ .../SquadFacilityBindInfoMessageTest.scala | 86 ++++++++++ 6 files changed, 316 insertions(+), 3 deletions(-) create mode 100644 src/main/scala/net/psforever/packet/game/SquadBindInfoMessage.scala create mode 100644 src/main/scala/net/psforever/packet/game/SquadFacilityBindInfoMessage.scala create mode 100644 src/test/scala/game/SquadBindInfoMessageTest.scala create mode 100644 src/test/scala/game/SquadFacilityBindInfoMessageTest.scala diff --git a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 54c4e3352..40747f86e 100644 --- a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -569,9 +569,9 @@ object GamePacketOpcode extends Enumeration { case 0xdf => game.ZoneLockInfoMessage.decode // OPCODES 0xe0-ef - case 0xe0 => noDecoder(SquadBindInfoMessage) + case 0xe0 => game.SquadBindInfoMessage.decode case 0xe1 => noDecoder(AudioSequenceMessage) - case 0xe2 => noDecoder(SquadFacilityBindInfoMessage) + case 0xe2 => game.SquadFacilityBindInfoMessage.decode case 0xe3 => game.ZoneForcedCavernConnectionsMessage.decode case 0xe4 => noDecoder(MissionActionMessage) case 0xe5 => noDecoder(MissionKillTriggerMessage) diff --git a/src/main/scala/net/psforever/packet/game/SquadBindInfoMessage.scala b/src/main/scala/net/psforever/packet/game/SquadBindInfoMessage.scala new file mode 100644 index 000000000..2dd4d4697 --- /dev/null +++ b/src/main/scala/net/psforever/packet/game/SquadBindInfoMessage.scala @@ -0,0 +1,39 @@ +// Copyright (c) 2025 PSForever +package net.psforever.packet.game + +import net.psforever.packet.game.SquadBindInfoMessage.SquadBindEntry +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ + +final case class SquadBindInfoMessage( + unk0: Int, // squad? + elements: Vector[SquadBindEntry], + ) extends PlanetSideGamePacket { + type Packet = SquadBindInfoMessage + def opcode = GamePacketOpcode.SquadBindInfoMessage + def encode = SquadBindInfoMessage.encode(this) +} + +object SquadBindInfoMessage extends Marshallable[SquadBindInfoMessage] { + + final case class SquadBindEntry( + unk0: Long, + unk1: Long, + unk2: Int, + unk3: Boolean, + ) + + + private implicit val squadBindEntryCodec: Codec[SquadBindEntry] = ( + ("unk0" | uint32L) :: + ("unk1" | uint32L) :: + ("unk2" | uint16L) :: + ("unk3" | bool) + ).as[SquadBindEntry] + + implicit val codec: Codec[SquadBindInfoMessage] = ( + ("unk0" | int32L) :: + ("squadBindEntries" | vectorOfN(int32L, squadBindEntryCodec)) + ).as[SquadBindInfoMessage] +} diff --git a/src/main/scala/net/psforever/packet/game/SquadFacilityBindInfoMessage.scala b/src/main/scala/net/psforever/packet/game/SquadFacilityBindInfoMessage.scala new file mode 100644 index 000000000..895212710 --- /dev/null +++ b/src/main/scala/net/psforever/packet/game/SquadFacilityBindInfoMessage.scala @@ -0,0 +1,27 @@ +// Copyright (c) 2025 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ + +final case class SquadFacilityBindInfoMessage( + unk0: Boolean, + unk1: Long, + unk2: Long, + unk3: Long + ) extends PlanetSideGamePacket { + type Packet = EmpireBenefitsMessage + def opcode = GamePacketOpcode.SquadFacilityBindInfoMessage + def encode = SquadFacilityBindInfoMessage.encode(this) +} + +object SquadFacilityBindInfoMessage extends Marshallable[SquadFacilityBindInfoMessage] { + + implicit val codec: Codec[SquadFacilityBindInfoMessage] = ( + ("unk0" | bool) :: + ("unk1" | uint32L) :: + ("unk2" | uint32L) :: + ("unk3" | uint32L) + ).as[SquadFacilityBindInfoMessage] +} diff --git a/src/main/scala/net/psforever/types/ExperienceType.scala b/src/main/scala/net/psforever/types/ExperienceType.scala index 4f6b94209..a5ca0bee4 100644 --- a/src/main/scala/net/psforever/types/ExperienceType.scala +++ b/src/main/scala/net/psforever/types/ExperienceType.scala @@ -1,4 +1,4 @@ -// Copyright (c) 2023 PSForever +// Copyright (c) 2023, 2025 PSForever package net.psforever.types import enumeratum.values.{IntEnum, IntEnumEntry} @@ -12,6 +12,7 @@ object ExperienceType extends IntEnum[ExperienceType] { val values: IndexedSeq[ExperienceType] = findValues case object Normal extends ExperienceType(value = 0) + case object Unk1 extends ExperienceType(value = 1) // lotsplay.gcap #33316 @ 596.403340s / asdf.gcap #26207 @ 157.773563s case object Support extends ExperienceType(value = 2) case object RabbitBall extends ExperienceType(value = 4) diff --git a/src/test/scala/game/SquadBindInfoMessageTest.scala b/src/test/scala/game/SquadBindInfoMessageTest.scala new file mode 100644 index 000000000..0daa32368 --- /dev/null +++ b/src/test/scala/game/SquadBindInfoMessageTest.scala @@ -0,0 +1,160 @@ +// Copyright (c) 2025 PSForever +package game + +import net.psforever.packet._ +import net.psforever.packet.game.SquadBindInfoMessage +import net.psforever.packet.game.SquadBindInfoMessage.SquadBindEntry +import org.specs2.mutable._ +import scodec.bits._ + +class SquadBindInfoMessageTest extends Specification { + + private val sample1 = hex"e0 00000000 04000000 000000000000000000000 08000000000000000000 08000000000000000000 10000000000000000000 0" + private val sample2 = hex"e0 01000000 06000000 000000000700000007008 08000000000000000000 08000000000000000000 060000000e0000000e01 05000000000000000000 04000000038000000800 4" + private val sample3 = hex"e0 01000000 08000000 000000000000000000000 08000000080000004004 08000000040000002002 06000000000000000000 04000000000000000000 02800000000000000000 01800000000000000000 00e0000000e0000000e0 1" + private val sample4 = hex"e0 ffffffff 0a000000 000000000000000000000 08000000000000000000 08000000000000000000 06000000000000000000 04000000070000000c00 82800000038000000700 41800000000000000000 00e00000000000000000 00800000000000000000 00480000003800000070 040" + + "decode sample1" in { + PacketCoding.decodePacket(sample1).require match { + case SquadBindInfoMessage(u0, elements) => + u0 mustEqual 0 + elements mustEqual Vector( + SquadBindEntry(0,0,0,false), + SquadBindEntry(1,0,0,false), + SquadBindEntry(2,0,0,false), + SquadBindEntry(8,0,0,false) + ) + case _ => + ko + } + } + + "encode sample1" in { + val msg = SquadBindInfoMessage( + unk0 = 0, + elements = Vector( + SquadBindEntry(0,0,0,false), + SquadBindEntry(1,0,0,false), + SquadBindEntry(2,0,0,false), + SquadBindEntry(8,0,0,false) + ) + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual sample1 + } + + "decode sample2" in { + PacketCoding.decodePacket(sample2).require match { + case SquadBindInfoMessage(u0, elements) => + u0 mustEqual 1 + elements mustEqual Vector( + SquadBindEntry(0,7,7,true), + SquadBindEntry(1,0,0,false), + SquadBindEntry(2,0,0,false), + SquadBindEntry(3,7,7,true), + SquadBindEntry(5,0,0,false), + SquadBindEntry(8,7,16,true) + ) + case _ => + ko + } + } + + "encode sample2" in { + val msg = SquadBindInfoMessage( + unk0 = 1, + elements = Vector( + SquadBindEntry(0,7,7,true), + SquadBindEntry(1,0,0,false), + SquadBindEntry(2,0,0,false), + SquadBindEntry(3,7,7,true), + SquadBindEntry(5,0,0,false), + SquadBindEntry(8,7,16,true) + ) + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual sample2 + } + + "decode sample3" in { + PacketCoding.decodePacket(sample3).require match { + case SquadBindInfoMessage(u0, elements) => + u0 mustEqual 1 + elements mustEqual Vector( + SquadBindEntry(0,0,0,false), + SquadBindEntry(1,1,8,true), + SquadBindEntry(2,1,8,true), + SquadBindEntry(3,0,0,false), + SquadBindEntry(4,0,0,false), + SquadBindEntry(5,0,0,false), + SquadBindEntry(6,0,0,false), + SquadBindEntry(7,7,7,true) + ) + case _ => + ko + } + } + + "encode sample3" in { + val msg = SquadBindInfoMessage( + unk0 = 1, + elements = Vector( + SquadBindEntry(0,0,0,false), + SquadBindEntry(1,1,8,true), + SquadBindEntry(2,1,8,true), + SquadBindEntry(3,0,0,false), + SquadBindEntry(4,0,0,false), + SquadBindEntry(5,0,0,false), + SquadBindEntry(6,0,0,false), + SquadBindEntry(7,7,7,true) + ) + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual sample3 + } + + "decode sample4" in { + PacketCoding.decodePacket(sample4).require match { + case SquadBindInfoMessage(u0, elements) => + u0 mustEqual -1 + elements mustEqual Vector( + SquadBindEntry(0,0,0,false), + SquadBindEntry(1,0,0,false), + SquadBindEntry(2,0,0,false), + SquadBindEntry(3,0,0,false), + SquadBindEntry(4,7,12,true), + SquadBindEntry(5,7,14,true), + SquadBindEntry(6,0,0,false), + SquadBindEntry(7,0,0,false), + SquadBindEntry(8,0,0,false), + SquadBindEntry(9,7,14,true) + ) + case _ => + ko + } + } + + "encode sample4" in { + val msg = SquadBindInfoMessage( + unk0 = -1, + elements = Vector( + SquadBindEntry(0,0,0,false), + SquadBindEntry(1,0,0,false), + SquadBindEntry(2,0,0,false), + SquadBindEntry(3,0,0,false), + SquadBindEntry(4,7,12,true), + SquadBindEntry(5,7,14,true), + SquadBindEntry(6,0,0,false), + SquadBindEntry(7,0,0,false), + SquadBindEntry(8,0,0,false), + SquadBindEntry(9,7,14,true) + ) + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual sample4 + } +} diff --git a/src/test/scala/game/SquadFacilityBindInfoMessageTest.scala b/src/test/scala/game/SquadFacilityBindInfoMessageTest.scala new file mode 100644 index 000000000..e757a2979 --- /dev/null +++ b/src/test/scala/game/SquadFacilityBindInfoMessageTest.scala @@ -0,0 +1,86 @@ +// Copyright (c) 2025 PSForever +package game + +import net.psforever.packet._ +import net.psforever.packet.game.SquadFacilityBindInfoMessage +import org.specs2.mutable._ +import scodec.bits._ + +class SquadFacilityBindInfoMessageTest extends Specification { + + private val sample1 = hex"e2 0 38000000 00000000 00000000 0" + private val sample2 = hex"e2 0 20000000 80000000 38000000 0" + private val sample3 = hex"e2 0 18000000 48000000 28000000 0" + + "decode sample1" in { + PacketCoding.decodePacket(sample1).require match { + case SquadFacilityBindInfoMessage(unk0, unk1, unk2, unk3) => + unk0 mustEqual false + unk1 mustEqual 7 + unk2 mustEqual 0 + unk3 mustEqual 0 + case _ => + ko + } + } + + "encode sample1" in { + val msg = SquadFacilityBindInfoMessage( + unk0 = false, + unk1 = 7, + unk2 = 0, + unk3 = 0 + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual sample1 + } + + "decode sample2" in { + PacketCoding.decodePacket(sample2).require match { + case SquadFacilityBindInfoMessage(unk0, unk1, unk2, unk3) => + unk0 mustEqual false + unk1 mustEqual 4 + unk2 mustEqual 16 + unk3 mustEqual 7 + case _ => + ko + } + } + + "encode sample2" in { + val msg = SquadFacilityBindInfoMessage( + unk0 = false, + unk1 = 4, + unk2 = 16, + unk3 = 7 + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual sample2 + } + + "decode sample3" in { + PacketCoding.decodePacket(sample3).require match { + case SquadFacilityBindInfoMessage(unk0, unk1, unk2, unk3) => + unk0 mustEqual false + unk1 mustEqual 3 + unk2 mustEqual 9 + unk3 mustEqual 5 + case _ => + ko + } + } + + "encode sample3" in { + val msg = SquadFacilityBindInfoMessage( + unk0 = false, + unk1 = 3, + unk2 = 9, + unk3 = 5 + ) + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual sample3 + } +}