diff --git a/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala b/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala index 622e1c38..4af09d80 100644 --- a/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala @@ -3,7 +3,8 @@ package net.psforever.packet.game import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectClass} import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} -import scodec.{Attempt, Codec, Err} +import scodec.bits.BitVector +import scodec.{Attempt, Codec, DecodeResult, Err} import scodec.codecs._ import shapeless.{::, HNil} @@ -40,15 +41,12 @@ case class ObjectCreateMessageParent(guid : PlanetSideGUID, * (The GM-level command `/sync` tests for objects that "do not match" between the server and the client. * It's implementation and scope are undefined.)
*
- * Knowing the object's class is essential for parsing the specific information passed by the `data` parameter.
- *
- * Exploration:
- * Can we build a `case class` "foo" that can accept the `objectClass` and the `data` and construct any valid object automatically? + * Knowing the object's class is essential for parsing the specific information passed by the `data` parameter. * @param streamLength the total length of the data that composes this packet in bits, excluding the opcode and end padding * @param objectClass the code for the type of object being constructed * @param guid the GUID this object will be assigned * @param parentInfo if defined, the relationship between this object and another object (its parent) - * @param data the data used to construct this type of object + * @param data if defined, the data used to construct this type of object */ case class ObjectCreateMessage(streamLength : Long, objectClass : Int, @@ -61,52 +59,133 @@ case class ObjectCreateMessage(streamLength : Long, } object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] { - type Pattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: Option[ConstructorData] :: HNil + type Pattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: HNil type outPattern = Long :: Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: Option[ConstructorData] :: HNil /** * Codec for formatting around the lack of parent data in the stream. */ - val noParent : Codec[Pattern] = ( - ("objectClass" | uintL(0xb)) >>:~ { cls => //11u - ("guid" | PlanetSideGUID.codec) :: //16u - ("data" | ObjectClass.selectDataCodec(cls)) - } - ).xmap[Pattern] ( + private val noParent : Codec[Pattern] = ( + ("objectClass" | uintL(0xb)) :: //11u + ("guid" | PlanetSideGUID.codec) //16u + ).xmap[Pattern]( { - case cls :: guid :: data :: HNil => - cls :: guid :: None :: data :: HNil - }, - { - case cls :: guid :: None :: data :: HNil => - cls :: guid :: data :: HNil + case cls :: guid :: HNil => + cls :: guid :: None :: HNil + }, { + case cls :: guid :: None :: HNil => + cls :: guid :: HNil } ) - /** * Codec for reading and formatting parent data from the stream. */ - val parent : Codec[Pattern] = ( + private val parent : Codec[Pattern] = ( ("parentGuid" | PlanetSideGUID.codec) :: //16u - (("objectClass" | uintL(0xb)) >>:~ { cls => //11u - ("guid" | PlanetSideGUID.codec) :: //16u - ("parentSlotIndex" | PacketHelpers.encodedStringSize) :: //8u or 16u - ("data" | ObjectClass.selectDataCodec(cls)) - }) - ).xmap[Pattern] ( + ("objectClass" | uintL(0xb)) :: //11u + ("guid" | PlanetSideGUID.codec) :: //16u + ("parentSlotIndex" | PacketHelpers.encodedStringSize) //8u or 16u + ).xmap[Pattern]( { - case pguid :: cls :: guid :: slot :: data :: HNil => - cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: data :: HNil - }, - { - case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: data :: HNil => - pguid :: cls :: guid :: slot :: data :: HNil + case pguid :: cls :: guid :: slot :: HNil => + cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil + }, { + case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil => + pguid :: cls :: guid :: slot :: HNil } ) /** - * Calculate the stream length in number of bits by factoring in the two variable fields.
+ * Take bit data and transform it into an object that expresses the important information of a game piece.
*
- * Constant fields have already been factored into the results. + * This function is fail-safe because it catches errors involving bad parsing of the bitstream data. + * Generally, the `Exception` messages themselves are not useful. + * The important parts are what the packet thought the object class should be and what it actually processed. + * The bit data that failed to parse is retained for debugging at a later time. + * @param objectClass the code for the type of object being constructed + * @param data the bitstream data + * @return the optional constructed object + */ + private def decodeData(objectClass : Int, data : BitVector) : Option[ConstructorData] = { + var out : Option[ConstructorData] = None + val copy = data.drop(0) + try { + val outOpt : Option[DecodeResult[_]] = ObjectClass.selectDataCodec(objectClass).decode(copy).toOption + if(outOpt.isDefined) + out = outOpt.get.value.asInstanceOf[ConstructorData.genericPattern] + } + catch { + case ex : Exception => + //catch and release, any sort of parse error + } + out + } + + /** + * Take the important information of a game piece and transform it into bit data.
+ *
+ * This function is fail-safe because it catches errors involving bad parsing of the object data. + * Generally, the `Exception` messages themselves are not useful. + * If parsing fails, all data pertinent to debugging the failure is retained in the constructor. + * @param objClass the code for the type of object being deconstructed + * @param obj the object data + * @return the bitstream data + */ + private def encodeData(objClass : Int, obj : ConstructorData) : BitVector = { + var out = BitVector.empty + try { + val outOpt : Option[BitVector] = ObjectClass.selectDataCodec(objClass).encode(Some(obj.asInstanceOf[ConstructorData])).toOption + if(outOpt.isDefined) + out = outOpt.get + } + catch { + case ex : Exception => + //catch and release, any sort of parse error + } + out + } + + /** + * Calculate the stream length in number of bits by factoring in the whole message in two portions. + * @param parentInfo if defined, information about the parent + * @param data the data length is indeterminate until it is read + * @return the total length of the stream in bits + */ + private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : BitVector) : Long = { + //knowable length + val first : Long = commonMsgLen(parentInfo) + //data length + var second : Long = data.size + val secondMod4 : Long = second % 4L + if(secondMod4 > 0L) { + //pad to include last whole nibble + second += 4L - secondMod4 + } + first + second + } + + /** + * Calculate the stream length in number of bits by factoring in the whole message in two portions. + * @param parentInfo if defined, information about the parent + * @param data the data length is indeterminate until it is read + * @return the total length of the stream in bits + */ + private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : ConstructorData) : Long = { + //knowable length + val first : Long = commonMsgLen(parentInfo) + //data length + var second : Long = data.bitsize + val secondMod4 : Long = second % 4L + if(secondMod4 > 0L) { + //pad to include last whole nibble + second += 4L - secondMod4 + } + first + second + } + + /** + * Calculate the length (in number of bits) of the basic packet message region.
+ *
+ * Ignoring the parent data, constant field lengths have already been factored into the results. * That includes: * the length of the stream length field (32u), * the object's class (11u), @@ -114,164 +193,58 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] { * and the bit to determine if there will be parent data. * In total, these fields form a known fixed length of 60u. * @param parentInfo if defined, the parentInfo adds either 24u or 32u - * @param data the data length is indeterminate until it is read - * @return the total length of the stream in bits + * @return the length, including the optional parent data */ - private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : Option[ConstructorData]) : Long = { - //msg length - val first : Long = if(parentInfo.isDefined) { //(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u)) + private def commonMsgLen(parentInfo : Option[ObjectCreateMessageParent]) : Long = { + if(parentInfo.isDefined) { + //(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u)) if(parentInfo.get.slot > 127) 92L else 84L } else { 60L } - //data length - var second : Long = if(data.isDefined) data.get.bsize else 0L - val secondMod4 : Long = second % 4L - if(secondMod4 > 0L) { //pad to include last whole nibble - second += 4L - secondMod4 - } - first + second } implicit val codec : Codec[ObjectCreateMessage] = ( ("streamLength" | uint32L) :: - either(bool, parent, noParent).exmap[Pattern] ( + (either(bool, parent, noParent).exmap[Pattern] ( { - case Left(a :: b :: Some(c) :: d :: HNil) => - Attempt.successful(a :: b :: Some(c) :: d :: HNil) //true, _, _, Some(c) - case Right(a :: b :: None :: d :: HNil) => - Attempt.successful(a :: b :: None :: d :: HNil) //false, _, _, None + case Left(a :: b :: Some(c) :: HNil) => + Attempt.successful(a :: b :: Some(c) :: HNil) //true, _, _, Some(c) + case Right(a :: b :: None :: HNil) => + Attempt.successful(a :: b :: None :: HNil) //false, _, _, None // failure cases - case Left(a :: b :: None :: _ :: HNil) => + case Left(a :: b :: None :: HNil) => Attempt.failure(Err("missing parent structure")) //true, _, _, None - case Right(a :: b :: Some(c) :: _ :: HNil) => + case Right(a :: b :: Some(c) :: HNil) => Attempt.failure(Err("unexpected parent structure")) //false, _, _, Some(c) - }, - { - case a :: b :: Some(c) :: d :: HNil => - Attempt.successful(Left(a :: b :: Some(c) :: d :: HNil)) - case a :: b :: None :: d :: HNil => - Attempt.successful(Right(a :: b :: None :: d :: HNil)) + }, { + case a :: b :: Some(c) :: HNil => + Attempt.successful(Left(a :: b :: Some(c) :: HNil)) + case a :: b :: None :: HNil => + Attempt.successful(Right(a :: b :: None :: HNil)) } - ) - ).xmap[outPattern] ( + ) :+ + ("data" | bits)) //greed is good + ).xmap[outPattern]( { case len :: cls :: guid :: par :: data :: HNil => - len :: cls :: guid :: par :: data :: HNil - }, + len :: cls :: guid :: par :: decodeData(cls, data) :: HNil + }, { + case _ :: cls :: guid :: par :: Some(obj) :: HNil => + streamLen(par, obj) :: cls :: guid :: par :: encodeData(cls, obj) :: HNil + case _ :: cls :: guid :: par :: None :: HNil => + streamLen(par, BitVector.empty) :: cls :: guid :: par :: BitVector.empty :: HNil + } + ).exmap[ObjectCreateMessage]( { - case _ :: cls :: guid :: par :: data :: HNil => - streamLen(par, data) :: cls :: guid :: par :: data :: HNil + case len :: cls :: guid :: par :: obj :: HNil => + Attempt.successful(ObjectCreateMessage(len, cls, guid, par, obj)) + }, { + case ObjectCreateMessage(_, _, _, _, None) => + Attempt.failure(Err("no object to encode")) + case ObjectCreateMessage(len, cls, guid, par, obj) => + Attempt.successful(len :: cls :: guid :: par :: obj :: HNil) } ).as[ObjectCreateMessage] -} - -//import net.psforever.packet.game.objectcreate.Mold -//import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} -//import scodec.bits._ -//import scodec.{Attempt, Codec, Err} -//import scodec.codecs._ -//import shapeless.{::, HNil} -// -//case class ObjectCreateMessageParent(guid : PlanetSideGUID, -// slot : Int) -// -//case class ObjectCreateMessage(streamLength : Long, -// objectClass : Int, -// guid : PlanetSideGUID, -// parentInfo : Option[ObjectCreateMessageParent], -// mold : Mold) -// extends PlanetSideGamePacket { -// def opcode = GamePacketOpcode.ObjectCreateMessage -// def encode = ObjectCreateMessage.encode(this) -//} -// -//object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] { -// type Pattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: HNil -// /** -// * Codec for formatting around the lack of parent data in the stream. -// */ -// val noParent : Codec[Pattern] = ( -// ("objectClass" | uintL(0xb)) :: //11u -// ("guid" | PlanetSideGUID.codec) //16u -// ).xmap[Pattern] ( -// { -// case cls :: guid :: HNil => -// cls :: guid :: None :: HNil -// }, -// { -// case cls :: guid :: None :: HNil => -// cls :: guid :: HNil -// } -// ) -// -// /** -// * Codec for reading and formatting parent data from the stream. -// */ -// val parent : Codec[Pattern] = ( -// ("parentGuid" | PlanetSideGUID.codec) :: //16u -// ("objectClass" | uintL(0xb)) :: //11u -// ("guid" | PlanetSideGUID.codec) :: //16u -// ("parentSlotIndex" | PacketHelpers.encodedStringSize) //8u or 16u -// ).xmap[Pattern] ( -// { -// case pguid :: cls :: guid :: slot :: HNil => -// cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil -// }, -// { -// case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil => -// pguid :: cls :: guid :: slot :: HNil -// } -// ) -// -// private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : BitVector) : Long = { -// //known length -// val first : Long = if(parentInfo.isDefined) { -// if(parentInfo.get.slot > 127) 92L else 84L //60u + 16u + (8u or 16u) -// } -// else { -// 60L -// } -// //variant length -// var second : Long = data.size -// val secondMod4 : Long = second % 4L -// if(secondMod4 > 0L) { //pad to include last whole nibble -// second += 4L - secondMod4 -// } -// first + second -// } -// -// implicit val codec : Codec[ObjectCreateMessage] = ( -// ("streamLength" | uint32L) :: -// (either(bool, parent, noParent).exmap[Pattern] ( -// { -// case Left(a :: b :: Some(c) :: HNil) => -// Attempt.successful(a :: b :: Some(c) :: HNil) //true, _, _, Some(c) -// case Right(a :: b :: None :: HNil) => -// Attempt.successful(a :: b :: None :: HNil) //false, _, _, None -// // failure cases -// case Left(a :: b :: None :: HNil) => -// Attempt.failure(Err("missing parent structure")) //true, _, _, None -// case Right(a :: b :: Some(c) :: HNil) => -// Attempt.failure(Err("unexpected parent structure")) //false, _, _, Some(c) -// }, -// { -// case a :: b :: Some(c) :: HNil => -// Attempt.successful(Left(a :: b :: Some(c) :: HNil)) -// case a :: b :: None :: HNil => -// Attempt.successful(Right(a :: b :: None :: HNil)) -// } -// ) :+ -// ("data" | bits) ) -// ).xmap[ObjectCreateMessage] ( -// { -// case len :: cls :: guid :: info :: data :: HNil => -// ObjectCreateMessage(len, cls, guid, info, Mold(cls, data)) -// }, -// { -// case ObjectCreateMessage(_, cls, guid, info, mold) => -// streamLen(info, mold.data) :: cls :: guid :: info :: mold.data :: HNil -// } -// ).as[ObjectCreateMessage] -//} +} \ No newline at end of file diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/AmmoBoxData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/AmmoBoxData.scala index 6958da56..054faac2 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/AmmoBoxData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/AmmoBoxData.scala @@ -8,7 +8,7 @@ import shapeless.{::, HNil} case class AmmoBoxData(magazine : Int ) extends ConstructorData { - override def bsize : Long = 39L + override def bitsize : Long = 39L } object AmmoBoxData extends Marshallable[AmmoBoxData] { diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala index 91ed7b75..57f3f07c 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/CharacterData.scala @@ -42,8 +42,8 @@ case class CharacterData(pos : Vector3, tutorial_list : List[String], inventory : InventoryData ) extends ConstructorData { - override def bsize : Long = { - //represents static fields + override def bitsize : Long = { + //represents static fields (includes medals.bitsize) val first : Long = 1194L //TODO due to changing understanding of the bit patterns in this data, this value will change //name val second : Long = CharacterData.stringBitSize(name, 16) + 4L //plus the padding @@ -60,7 +60,7 @@ case class CharacterData(pos : Vector3, for(str <- tutorial_list) { fourth += CharacterData.stringBitSize(str) } - first + second + third + fourth + inventory.bsize + first + second + third + fourth + inventory.bitsize } } diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ConstructorData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ConstructorData.scala index 9cd35252..58a978bd 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ConstructorData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ConstructorData.scala @@ -2,7 +2,7 @@ package net.psforever.packet.game.objectcreate abstract class ConstructorData() { - def bsize : Long = 0L + def bitsize : Long = 0L } object ConstructorData { diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/InternalSlot.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/InternalSlot.scala index ed8ab873..01b9171b 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/InternalSlot.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/InternalSlot.scala @@ -19,9 +19,9 @@ case class InternalSlot(objectClass : Int, guid : PlanetSideGUID, parentSlot : Int, obj : Option[ConstructorData]) { - def bsize : Long = { + def bitsize : Long = { val first : Long = if(parentSlot > 127) 44L else 36L - val second : Long = if(obj.isDefined) obj.get.bsize else 0L + val second : Long = if(obj.isDefined) obj.get.bitsize else 0L first + second } } diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryData.scala index 161bbf0b..4f68764a 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryData.scala @@ -3,14 +3,13 @@ package net.psforever.packet.game.objectcreate import net.psforever.packet.{Marshallable, PacketHelpers} import scodec.Codec -import scodec.bits.BitVector import scodec.codecs._ case class InventoryData(unk1 : Boolean, size : Int, unk2 : Boolean){//, //inv : List[InventoryItem]) { - def bsize : Long = { + def bitsize : Long = { 10L } } @@ -18,7 +17,7 @@ case class InventoryData(unk1 : Boolean, object InventoryData extends Marshallable[InventoryData] { implicit val codec : Codec[InventoryData] = ( ("unk1" | bool) :: - (("size" | uint8L) >>:~ { len => + (("len" | uint8L) >>:~ { len => ("unk2" | bool).hlist// :: //("inv" | PacketHelpers.listOfNSized(len, InventoryItem.codec)) }) diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/Mold.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/Mold.scala deleted file mode 100644 index eb75953c..00000000 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/Mold.scala +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2016 PSForever.net to present -package net.psforever.packet.game.objectcreate - -import scodec.DecodeResult -import scodec.bits.BitVector - -case class Mold(objectClass : Int, - data : BitVector) { - private var obj : Option[ConstructorData] = Mold.decode(objectClass, data) - - def isDefined : Boolean = this.obj.isDefined - - def get : ConstructorData = this.obj.get - - def set(data : ConstructorData) : Boolean = { - var ret = false - if(Some(data).isDefined) { - obj = Some(data) - ret = true - } - ret - } -} - -object Mold { - def apply(objectClass : Int, obj : ConstructorData) : Mold = - new Mold( objectClass, Mold.encode(objectClass, obj) ) - - def decode(objClass : Int, data : BitVector) : Option[ConstructorData] = { - var out : Option[ConstructorData] = None - if(!data.isEmpty) { - val codec = ObjectClass.selectDataCodec(objClass) - var outOpt : Option[DecodeResult[_]] = None - try { - outOpt = codec.decode(data).toOption - if(outOpt.isDefined) - out = Some(outOpt.get.value.asInstanceOf[ConstructorData]) - } - catch { - case ex : ClassCastException => - //TODO generate and log wrong class error message - case ex : Exception => - //TODO generic error - } - } - out - } - - def encode(objClass : Int, obj : ConstructorData) : BitVector = { - var out = BitVector.empty - try { - val codec = ObjectClass.selectDataCodec(objClass) - var outOpt : Option[BitVector] = None - outOpt = codec.encode(obj.asInstanceOf[ConstructorData.genericPattern]).toOption - if(outOpt.isDefined) - out = outOpt.get - } - catch { - case ex : ClassCastException => - //TODO generate and log wrong class error message - case ex : Exception => - //TODO generic error - } - out - } -} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala index 9e4ae3ed..dce00ef9 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala @@ -1,7 +1,8 @@ // Copyright (c) 2016 PSForever.net to present package net.psforever.packet.game.objectcreate -import scodec.Codec +import scodec.{Attempt, Codec, Err} +import scodec.codecs._ import scala.annotation.switch @@ -36,18 +37,17 @@ object ObjectClass { case ObjectClass.SUPPRESSOR => WeaponData.genericCodec case ObjectClass.REK => REKData.genericCodec case ObjectClass.SLOT_BLOCKER => AmmoBoxData.genericCodec - case _ => RecoveredData.genericCodec + //failure case + case _ => conditional(false, bool).exmap[ConstructorData.genericPattern] ( + { + case None | _ => + Attempt.failure(Err("decoding unknown object class - "+objClass)) + }, + { + case None | _ => + Attempt.failure(Err("encoding unknown object class - "+objClass)) + } + ) } } - -// val failureCodec : Codec[ConstructorData.genericPattern] = conditional(false, bool).exmap[ConstructorData.genericPattern] ( -// { -// case None | _ => -// Attempt.failure(Err("object class unrecognized during decoding")) -// }, -// { -// case None | _ => -// Attempt.failure(Err("object class unrecognized during encoding")) -// } -// ) -} \ No newline at end of file +} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/REKData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/REKData.scala index ef396c83..e1e838ea 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/REKData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/REKData.scala @@ -8,7 +8,7 @@ import shapeless.{::, HNil} case class REKData(unk : Int ) extends ConstructorData { - override def bsize : Long = 72L + override def bitsize : Long = 72L } object REKData extends Marshallable[REKData] { diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/RecoveredData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/RecoveredData.scala deleted file mode 100644 index 04cda576..00000000 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/RecoveredData.scala +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright (c) 2016 PSForever.net to present -package net.psforever.packet.game.objectcreate - -import net.psforever.packet.Marshallable -import scodec.{Attempt, Codec, Err} -import scodec.codecs._ -import scodec.bits.BitVector - -case class RecoveredData(data : BitVector - ) extends ConstructorData { - override def bsize : Long = data.size -} - -object RecoveredData extends Marshallable[RecoveredData] { - implicit val codec : Codec[RecoveredData] = ( - "data" | bits - ).as[RecoveredData] - - val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] ( - { - case _ => - Attempt.failure(Err("un-parsed byte data preserved when decoding failed")) - }, - { - case _ => - Attempt.failure(Err("can not encode object")) - } - ) -} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/RibbonBars.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/RibbonBars.scala index 7b9275a1..a1193299 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/RibbonBars.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/RibbonBars.scala @@ -8,7 +8,9 @@ import scodec.codecs._ case class RibbonBars(upper : Long = 0xFFFFFFFFL, //0xFFFFFFFF means no merit (for all ...) middle : Long = 0xFFFFFFFFL, lower : Long = 0xFFFFFFFFL, - tos : Long = 0xFFFFFFFFL) + tos : Long = 0xFFFFFFFFL) { + def bitsize : Long = 128L +} object RibbonBars extends Marshallable[RibbonBars] { implicit val codec : Codec[RibbonBars] = ( diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala index a1c21aca..245205c8 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala @@ -9,7 +9,7 @@ import shapeless.{::, HNil} case class WeaponData(unk : Int, ammo : InternalSlot) extends ConstructorData { - override def bsize : Long = 59L + ammo.bsize + override def bitsize : Long = 59L + ammo.bitsize } object WeaponData extends Marshallable[WeaponData] { diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala index efdc3a93..8498e1e6 100644 --- a/common/src/test/scala/GamePacketTest.scala +++ b/common/src/test/scala/GamePacketTest.scala @@ -148,6 +148,8 @@ class GamePacketTest extends Specification { "ObjectCreateMessage" should { val packet = hex"18 CF 13 00 00 BC 87 00 0A F0 16 C3 43 A1 30 90 00 02 C0 40 00 08 70 43 00 68 00 6F 00 72 00 64 00 54 00 52 00 82 65 1F F5 9E 80 80 00 00 00 00 00 3F FF C0 00 00 00 20 00 00 00 20 27 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FC CC 10 00 03 20 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 00 00 00 01 00 7E C8 00 C8 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 C0 00 42 C5 46 86 C7 00 00 02 A0 00 00 12 60 78 70 65 5F 77 61 72 70 5F 67 61 74 65 5F 75 73 61 67 65 92 78 70 65 5F 69 6E 73 74 61 6E 74 5F 61 63 74 69 6F 6E 92 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 91 78 70 65 5F 62 61 74 74 6C 65 5F 72 61 6E 6B 5F 32 8E 78 70 65 5F 66 6F 72 6D 5F 73 71 75 61 64 8E 78 70 65 5F 74 68 5F 6E 6F 6E 73 61 6E 63 8B 78 70 65 5F 74 68 5F 61 6D 6D 6F 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8F 75 73 65 64 5F 63 68 61 69 6E 62 6C 61 64 65 9A 76 69 73 69 74 65 64 5F 62 72 6F 61 64 63 61 73 74 5F 77 61 72 70 67 61 74 65 8E 76 69 73 69 74 65 64 5F 6C 6F 63 6B 65 72 8D 75 73 65 64 5F 70 75 6E 69 73 68 65 72 88 75 73 65 64 5F 72 65 6B 8D 75 73 65 64 5F 72 65 70 65 61 74 65 72 9F 76 69 73 69 74 65 64 5F 64 65 63 6F 6E 73 74 72 75 63 74 69 6F 6E 5F 74 65 72 6D 69 6E 61 6C 8F 75 73 65 64 5F 73 75 70 70 72 65 73 73 6F 72 96 76 69 73 69 74 65 64 5F 6F 72 64 65 72 5F 74 65 72 6D 69 6E 61 6C 85 6D 61 70 31 35 85 6D 61 70 31 34 85 6D 61 70 31 32 85 6D 61 70 30 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 0A 36 13 88 04 00 40 00 00 10 00 04 00 00 4D 6E 40 10 41 00 00 00 40 00 18 08 38 1C C0 20 32 00 00 07 80 15 E1 D0 02 10 20 00 00 08 00 03 01 07 13 A8 04 06 40 00 00 10 03 20 BB 00 42 E4 00 00 01 00 0E 07 70 08 6C 80 00 06 40 01 C0 F0 01 13 90 00 00 C8 00 38 1E 40 23 32 00 00 19 00 07 03 D0 05 0E 40 00 03 20 00 E8 7B 00 A4 C8 00 00 64 00 DA 4F 80 14 E1 00 00 00 40 00 18 08 38 1F 40 20 32 00 00 0A 00 08 " //fake data? + val packet2 = hex"18 F8 00 00 00 BC 8C 10 90 3B 45 C6 FA 94 00 9F F0 00 00 40 00 08 C0 44 00 69 00 66 00 66 00 45" //fake data + //val packet2Rest = packet2.bits.drop(8 + 32 + 1 + 11 + 16) var string_inventoryItem = hex"46 04 C0 08 08 80 00 00 20 00 0C 04 10 29 A0 10 19 00 00 04 00 00" val string_9mm = hex"18 7C000000 2580 0E0 0005 A1 C8000064000" val string_gauss = hex"18 DC000000 2580 2C9 B905 82 480000020000C04 1C00C0B0190000078000" @@ -174,6 +176,20 @@ class GamePacketTest extends Specification { } } + "decode (2)" in { + //an invalid bit representation will fail to turn into an object + PacketCoding.DecodePacket(packet2).require match { + case obj @ ObjectCreateMessage(len, cls, guid, parent, data) => + len mustEqual 248 //60 + 188 + cls mustEqual 121 + guid mustEqual PlanetSideGUID(2497) + parent mustEqual None + data.isDefined mustEqual false + case default => + ko + } + } + "decode (char)" in { PacketCoding.DecodePacket(string_testchar).require match { case obj @ ObjectCreateMessage(len, cls, guid, parent, data) => @@ -236,15 +252,15 @@ class GamePacketTest extends Specification { "decode (9mm)" in { PacketCoding.DecodePacket(string_9mm).require match { - case obj @ ObjectCreateMessage(len, cls, guid, parent, mold) => + case obj @ ObjectCreateMessage(len, cls, guid, parent, data) => len mustEqual 124 cls mustEqual 28 guid mustEqual PlanetSideGUID(1280) parent.isDefined mustEqual true parent.get.guid mustEqual PlanetSideGUID(75) parent.get.slot mustEqual 33 - mold.isDefined mustEqual true - val obj = mold.get.asInstanceOf[AmmoBoxData] + data.isDefined mustEqual true + val obj = data.get.asInstanceOf[AmmoBoxData] obj.magazine mustEqual 50 case default => ko @@ -253,15 +269,15 @@ class GamePacketTest extends Specification { "decode (gauss)" in { PacketCoding.DecodePacket(string_gauss).require match { - case obj @ ObjectCreateMessage(len, cls, guid, parent, mold) => + case obj @ ObjectCreateMessage(len, cls, guid, parent, data) => len mustEqual 220 cls mustEqual 345 guid mustEqual PlanetSideGUID(1465) parent.isDefined mustEqual true parent.get.guid mustEqual PlanetSideGUID(75) parent.get.slot mustEqual 2 - mold.isDefined mustEqual true - val obj_wep = mold.get.asInstanceOf[WeaponData] + data.isDefined mustEqual true + val obj_wep = data.get.asInstanceOf[WeaponData] obj_wep.unk mustEqual 4 val obj_ammo = obj_wep.ammo//.asInstanceOf[InternalSlot] obj_ammo.objectClass mustEqual 28 @@ -275,6 +291,12 @@ class GamePacketTest extends Specification { } } + "encode (2)" in { + //the lack of an object will fail to turn into a bad bitstream + val msg = ObjectCreateMessage(0, 121, PlanetSideGUID(2497), None, None) + PacketCoding.EncodePacket(msg).isFailure mustEqual true + } + "encode (9mm)" in { val obj : ConstructorData = AmmoBoxData(50).asInstanceOf[ConstructorData] val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 33)), Some(obj))