diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 6e9984e5..22eb0489 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -455,7 +455,7 @@ object GamePacketOpcode extends Enumeration { case 0x70 => noDecoder(SquadMemberEvent) case 0x71 => noDecoder(PlatoonEvent) case 0x72 => noDecoder(FriendsRequest) - case 0x73 => noDecoder(FriendsResponse) + case 0x73 => game.FriendsReponse.decode case 0x74 => noDecoder(TriggerEnvironmentalDamageMessage) case 0x75 => noDecoder(TrainingZoneMessage) case 0x76 => noDecoder(DeployableObjectsInfoMessage) diff --git a/common/src/main/scala/net/psforever/packet/PSPacket.scala b/common/src/main/scala/net/psforever/packet/PSPacket.scala index 2fe9a563..e59cf734 100644 --- a/common/src/main/scala/net/psforever/packet/PSPacket.scala +++ b/common/src/main/scala/net/psforever/packet/PSPacket.scala @@ -203,4 +203,17 @@ object PacketHelpers { def encodedStringWithLimit(limit : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithLimit(limit), ascii) */ + + /** + * Construct a `Codec` for reading `wchar_t` (wide character) `Strings` whose length field are constrained to specific bit size proportions. + * Padding may also exist between the length field and the beginning of the contents. + * @param lenSize a codec that defines the bit size that encodes the length + * @param adjustment the optional alignment for padding; defaults to 0 + * @return the `String` `Codec` + */ + def specSizeWideStringAligned(lenSize : Codec[Int], adjustment : Int = 0) : Codec[String] = + variableSizeBytes((lenSize <~ ignore(adjustment)).xmap( + insize => insize*2, // number of symbols -> number of bytes (decode) + outSize => outSize/2 // number of bytes -> number of symbols (encode) + ), utf16) } diff --git a/common/src/main/scala/net/psforever/packet/game/FriendsResponse.scala b/common/src/main/scala/net/psforever/packet/game/FriendsResponse.scala new file mode 100644 index 00000000..402f194d --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/FriendsResponse.scala @@ -0,0 +1,29 @@ +// Copyright (c) 2016 PSForever.net to present +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ + +/** + * na + * @param player_guid the player + * @param friend the name of the friend + * @param unk na + */ +final case class FriendsResponse(player_guid : PlanetSideGUID, + friend : String, + unk : Int) + extends PlanetSideGamePacket { + type Packet = FriendsResponse + def opcode = GamePacketOpcode.FriendsResponse + def encode = FriendsReponse.encode(this) +} + +object FriendsReponse extends Marshallable[FriendsResponse] { + implicit val codec : Codec[FriendsResponse] = ( + ("player_guid" | PlanetSideGUID.codec) :: + ("friend" | PacketHelpers.specSizeWideStringAligned(uint(5), 3)) :: + ("unk" | uint8L) + ).as[FriendsResponse] +} diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala index 2f7e8a11..a90801fd 100644 --- a/common/src/test/scala/GamePacketTest.scala +++ b/common/src/test/scala/GamePacketTest.scala @@ -830,6 +830,29 @@ class GamePacketTest extends Specification { } } + "FriendsResponse" should { + val string = hex"73 618C 60 4B007500720074004800650063007400690063002D004700 00" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case FriendsResponse(player_guid, friend, unk) => + player_guid mustEqual PlanetSideGUID(35937) + friend.length mustEqual 12 + friend mustEqual "KurtHectic-G" + unk mustEqual 0 + case default => + ko + } + } + + "encode" in { + val msg = FriendsResponse(PlanetSideGUID(35937), "KurtHectic-G", 0) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } + } + "WeaponDryFireMessage" should { val string = hex"52 4C00"