diff --git a/common/src/main/scala/net/psforever/packet/PSPacket.scala b/common/src/main/scala/net/psforever/packet/PSPacket.scala index 2fe9a563..12d79609 100644 --- a/common/src/main/scala/net/psforever/packet/PSPacket.scala +++ b/common/src/main/scala/net/psforever/packet/PSPacket.scala @@ -203,4 +203,27 @@ object PacketHelpers { def encodedStringWithLimit(limit : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithLimit(limit), ascii) */ + + def listOfNAligned[A](countCodec: Codec[Int], alignment : Int, valueCodec: Codec[A]): Codec[List[A]] = { + countCodec. + flatZip { count => new AlignedListCodec(valueCodec, alignment, Some(count)) }. + narrow[List[A]]({ case (cnt, xs) => + if (xs.size == cnt) Attempt.successful(xs) + else Attempt.failure(Err(s"Insufficient number of elements: decoded ${xs.size} but should have decoded $cnt")) + }, xs => (xs.size, xs)). + withToString(s"listOfN($countCodec, $valueCodec)") + } +} + +private final class AlignedListCodec[A](codec: Codec[A], alignment : Int, limit: Option[Int] = None) extends Codec[List[A]] { + def sizeBound = limit match { + case None => SizeBound.unknown + case Some(lim) => codec.sizeBound * lim.toLong + } + + def encode(list: List[A]) = Encoder.encodeSeq(codec)(list) + + def decode(buffer: BitVector) = Decoder.decodeCollect[List, A](codec, limit)(buffer.drop(alignment)) + + override def toString = s"list($codec)" } diff --git a/common/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala index dedccd92..ec0f4f45 100644 --- a/common/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala @@ -1,7 +1,7 @@ // Copyright (c) 2016 PSForever.net to present package net.psforever.packet.game -import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import scodec.Codec import scodec.codecs._ @@ -41,6 +41,7 @@ final case class HotSpotInfo(unk1 : Int, * @param spots a List of HotSpotInfo, or `Nil` if empty */ // TODO need aligned/padded list support +// TODO test with sendRawResponse(hex"9F 0D00 5 02 0 00 D07 00 8CA 00020 00 BEA 00 4C4 40000") final case class HotSpotUpdateMessage(continent_guid : PlanetSideGUID, unk : Int, spots : List[HotSpotInfo] = Nil) @@ -64,6 +65,6 @@ object HotSpotUpdateMessage extends Marshallable[HotSpotUpdateMessage] { implicit val codec : Codec[HotSpotUpdateMessage] = ( ("continent_guid" | PlanetSideGUID.codec) :: ("unk" | uint4L) :: - ("spots" | listOfN(uint8L, HotSpotInfo.codec)) + ("spots" | PacketHelpers.listOfNAligned(uint8L, 4, HotSpotInfo.codec)) ).as[HotSpotUpdateMessage] } diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala index 230a0d6f..e1afabaa 100644 --- a/common/src/test/scala/GamePacketTest.scala +++ b/common/src/test/scala/GamePacketTest.scala @@ -937,8 +937,8 @@ class GamePacketTest extends Specification { continent_guid mustEqual PlanetSideGUID(5) unk mustEqual 1 spots.size mustEqual 1 - spots.head.x mustEqual 3730 - spots.head.y mustEqual 1105 + spots.head.x mustEqual 2350 + spots.head.y mustEqual 1300 spots.head.scale mustEqual 128 case _ => ko @@ -951,11 +951,11 @@ class GamePacketTest extends Specification { continent_guid mustEqual PlanetSideGUID(5) unk mustEqual 5 spots.size mustEqual 2 - spots.head.x mustEqual 125 - spots.head.y mustEqual 3240 + spots.head.x mustEqual 2000 + spots.head.y mustEqual 2700 spots.head.scale mustEqual 128 - spots(1).x mustEqual 3755 - spots(1).y mustEqual 3140 + spots(1).x mustEqual 2750 + spots(1).y mustEqual 1100 spots(1).scale mustEqual 128 case _ => ko @@ -969,13 +969,13 @@ class GamePacketTest extends Specification { } "encode (one)" in { - val msg = HotSpotUpdateMessage(PlanetSideGUID(5),1, HotSpotInfo(0,3730,0,1105,128)::Nil) + val msg = HotSpotUpdateMessage(PlanetSideGUID(5),1, HotSpotInfo(0,2350,0,1300,128)::Nil) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringOne } "encode (two)" in { - val msg = HotSpotUpdateMessage(PlanetSideGUID(5),1, HotSpotInfo(0,125,0,3240,128)::HotSpotInfo(0,3755,0,3140,128)::Nil) + val msg = HotSpotUpdateMessage(PlanetSideGUID(5),1, HotSpotInfo(0,2000,0,2700,128)::HotSpotInfo(0,2750,0,1100,128)::Nil) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringOne }