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))