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 32af6453c..622e1c38f 100644 --- a/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala @@ -1,9 +1,8 @@ // Copyright (c) 2016 PSForever.net to present package net.psforever.packet.game -import net.psforever.packet.game.objectcreate.Mold +import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectClass} import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} -import scodec.bits._ import scodec.{Attempt, Codec, Err} import scodec.codecs._ import shapeless.{::, HNil} @@ -49,35 +48,37 @@ case class ObjectCreateMessageParent(guid : PlanetSideGUID, * @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 mold the data used to construct this type of object; - * requires further object-specific processing + * @param data the data used to construct this type of object */ case class ObjectCreateMessage(streamLength : Long, objectClass : Int, guid : PlanetSideGUID, parentInfo : Option[ObjectCreateMessageParent], - mold : Mold) + data : Option[ConstructorData]) extends PlanetSideGamePacket { def opcode = GamePacketOpcode.ObjectCreateMessage def encode = ObjectCreateMessage.encode(this) } object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] { - type Pattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: HNil + type Pattern = Int :: PlanetSideGUID :: Option[ObjectCreateMessageParent] :: Option[ConstructorData] :: 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)) :: //11u - ("guid" | PlanetSideGUID.codec) //16u + ("objectClass" | uintL(0xb)) >>:~ { cls => //11u + ("guid" | PlanetSideGUID.codec) :: //16u + ("data" | ObjectClass.selectDataCodec(cls)) + } ).xmap[Pattern] ( { - case cls :: guid :: HNil => - cls :: guid :: None :: HNil + case cls :: guid :: data :: HNil => + cls :: guid :: None :: data :: HNil }, { - case cls :: guid :: None :: HNil => - cls :: guid :: HNil + case cls :: guid :: None :: data :: HNil => + cls :: guid :: data :: HNil } ) @@ -86,17 +87,19 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] { */ val parent : Codec[Pattern] = ( ("parentGuid" | PlanetSideGUID.codec) :: //16u - ("objectClass" | uintL(0xb)) :: //11u - ("guid" | PlanetSideGUID.codec) :: //16u - ("parentSlotIndex" | PacketHelpers.encodedStringSize) //8u or 16u + (("objectClass" | uintL(0xb)) >>:~ { cls => //11u + ("guid" | PlanetSideGUID.codec) :: //16u + ("parentSlotIndex" | PacketHelpers.encodedStringSize) :: //8u or 16u + ("data" | ObjectClass.selectDataCodec(cls)) + }) ).xmap[Pattern] ( { - case pguid :: cls :: guid :: slot :: HNil => - cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil + case pguid :: cls :: guid :: slot :: data :: HNil => + cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: data :: HNil }, { - case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil => - pguid :: cls :: guid :: slot :: HNil + case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: data :: HNil => + pguid :: cls :: guid :: slot :: data :: HNil } ) @@ -114,16 +117,16 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] { * @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 = { - //known length - val first : Long = if(parentInfo.isDefined) { - if(parentInfo.get.slot > 127) 92L else 84L //60u + 16u + (8u or 16u) + 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)) + if(parentInfo.get.slot > 127) 92L else 84L } else { 60L } - //variant length - var second : Long = data.size + //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 @@ -133,34 +136,142 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] { 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) :: 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 + 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 // 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) :: HNil => - Attempt.successful(Left(a :: b :: Some(c) :: HNil)) - case a :: b :: None :: HNil => - Attempt.successful(Right(a :: b :: None :: HNil)) + 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)) } - ) :+ - ("data" | bits) ) - ).xmap[ObjectCreateMessage] ( + ) + ).xmap[outPattern] ( { - case len :: cls :: guid :: info :: data :: HNil => - ObjectCreateMessage(len, cls, guid, info, Mold(cls, data)) + case len :: cls :: guid :: par :: data :: HNil => + len :: cls :: guid :: par :: data :: HNil }, { - case ObjectCreateMessage(_, cls, guid, info, mold) => - streamLen(info, mold.data) :: cls :: guid :: info :: mold.data :: HNil + case _ :: cls :: guid :: par :: data :: HNil => + streamLen(par, data) :: cls :: guid :: par :: data :: 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] +//} 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 faa582b61..6958da560 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 @@ -6,7 +6,10 @@ import scodec.{Attempt, Codec, Err} import scodec.codecs._ import shapeless.{::, HNil} -case class AmmoBoxData(magazine : Int) extends ConstructorData +case class AmmoBoxData(magazine : Int + ) extends ConstructorData { + override def bsize : Long = 39L +} object AmmoBoxData extends Marshallable[AmmoBoxData] { implicit val codec : Codec[AmmoBoxData] = ( @@ -24,5 +27,18 @@ object AmmoBoxData extends Marshallable[AmmoBoxData] { case AmmoBoxData(mag) => Attempt.successful(0xC8 :: () :: mag :: HNil) } - ).as[AmmoBoxData] + ) + + val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] ( + { + case x => + Attempt.successful(Some(x.asInstanceOf[ConstructorData])) + }, + { + case Some(x) => + Attempt.successful(x.asInstanceOf[AmmoBoxData]) + case _ => + Attempt.failure(Err("")) + } + ) } 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 ddf8ca6c8..91ed7b758 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 @@ -3,7 +3,7 @@ package net.psforever.packet.game.objectcreate import net.psforever.packet.{Marshallable, PacketHelpers} import net.psforever.types.Vector3 -import scodec.Codec +import scodec.{Attempt, Codec, Err} import scodec.codecs._ case class CharacterData(pos : Vector3, @@ -41,9 +41,36 @@ case class CharacterData(pos : Vector3, firstTimeEvent_list : List[String], tutorial_list : List[String], inventory : InventoryData - ) extends ConstructorData + ) extends ConstructorData { + override def bsize : Long = { + //represents static fields + 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 + //fte_list + var third : Long = 32L + if(firstEntry.isDefined) { + third += CharacterData.stringBitSize(firstEntry.get) + 5L //plus the padding + for(str <- firstTimeEvent_list) { + third += CharacterData.stringBitSize(str) + } + } + //tutorial list + var fourth : Long = 32L + for(str <- tutorial_list) { + fourth += CharacterData.stringBitSize(str) + } + first + second + third + fourth + inventory.bsize + } +} object CharacterData extends Marshallable[CharacterData] { + private def stringBitSize(str : String, width : Int = 8) : Long = { + val strlen = str.length + val lenSize = if(strlen > 127) 16L else 8L + lenSize + strlen * width + } + implicit val codec : Codec[CharacterData] = ( ("pos" | Vector3.codec_pos) :: ignore(16) :: @@ -56,7 +83,7 @@ object CharacterData extends Marshallable[CharacterData] { ("exosuit" | uintL(3)) :: ignore(2) :: ("sex" | uintL(2)) :: - ("face1" | uint8L) :: + ("face1" | uint4L) :: ("face2" | uint4L) :: ("voice" | uintL(3)) :: ignore(22) :: @@ -98,4 +125,17 @@ object CharacterData extends Marshallable[CharacterData] { ("inventory" | InventoryData.codec) }) ).as[CharacterData] + + val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] ( + { + case x => + Attempt.successful(Some(x.asInstanceOf[ConstructorData])) + }, + { + case Some(x) => + Attempt.successful(x.asInstanceOf[CharacterData]) + case _ => + Attempt.failure(Err("")) + } + ) } 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 2b4d9cf6d..9cd352527 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 @@ -1,4 +1,10 @@ // Copyright (c) 2016 PSForever.net to present package net.psforever.packet.game.objectcreate -abstract class ConstructorData +abstract class ConstructorData() { + def bsize : Long = 0L +} + +object ConstructorData { + type genericPattern = Option[ConstructorData] +} \ No newline at end of file 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 704cdfe57..ed8ab873e 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 @@ -3,8 +3,7 @@ package net.psforever.packet.game.objectcreate import net.psforever.packet.{Marshallable, PacketHelpers} import net.psforever.packet.game.PlanetSideGUID -import scodec.{Attempt, Codec, Err} -import scodec.bits.BitVector +import scodec.Codec import scodec.codecs._ import shapeless.{::, HNil} @@ -19,40 +18,23 @@ import shapeless.{::, HNil} case class InternalSlot(objectClass : Int, guid : PlanetSideGUID, parentSlot : Int, - obj : Option[ConstructorData]) + obj : Option[ConstructorData]) { + def bsize : Long = { + val first : Long = if(parentSlot > 127) 44L else 36L + val second : Long = if(obj.isDefined) obj.get.bsize else 0L + first + second + } +} object InternalSlot extends Marshallable[InternalSlot] { - type objPattern = Int :: PlanetSideGUID :: Int :: Option[ConstructorData] :: HNil + type objPattern = Int :: PlanetSideGUID :: Int :: ConstructorData :: HNil implicit val codec : Codec[InternalSlot] = ( ignore(1) :: //TODO determine what this bit does - ("objectClass" | uintL(11)) :: - ("guid" | PlanetSideGUID.codec) :: - ("parentSlot" | PacketHelpers.encodedStringSize) :: - bits - ).exmap[objPattern] ( - { - case _ :: cls :: guid :: slot :: data :: HNil => - Attempt.successful(cls :: guid :: slot :: Mold.decode(cls, data) :: HNil) - }, - { - case cls :: guid :: slot :: None :: HNil => - Attempt.failure(Err("no constructor data could be found")) - case cls :: guid :: slot :: mold :: HNil => - Attempt.successful(() :: cls :: guid :: slot :: Mold.encode(cls, mold.get) :: HNil) - } - ).exmap[objPattern] ( - { - case cls :: guid :: slot :: None :: HNil => - Attempt.failure(Err("no decoded constructor data")) - case cls :: guid :: slot :: mold :: HNil => - Attempt.successful(cls :: guid :: slot :: mold :: HNil) - }, - { - case cls :: guid :: slot :: BitVector.empty :: HNil => - Attempt.failure(Err("no encoded constructor data")) - case cls :: guid :: slot :: data :: HNil => - Attempt.successful(cls :: guid :: slot :: data :: HNil) - } - ).as[InternalSlot] -} + (("objectClass" | uintL(11)) >>:~ { obj_cls => + ("guid" | PlanetSideGUID.codec) :: + ("parentSlot" | PacketHelpers.encodedStringSize) :: + ("obj" | ObjectClass.selectDataCodec(obj_cls)) + }) + ).as[InternalSlot] +} \ No newline at end of file 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 8e4f90e8e..161bbf0b1 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,19 +3,24 @@ 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]) + unk2 : Boolean){//, + //inv : List[InventoryItem]) { + def bsize : Long = { + 10L + } +} object InventoryData extends Marshallable[InventoryData] { implicit val codec : Codec[InventoryData] = ( ("unk1" | bool) :: (("size" | uint8L) >>:~ { len => - ("unk2" | bool) :: - ("inv" | PacketHelpers.listOfNSized(len, InventoryItem.codec)) + ("unk2" | bool).hlist// :: + //("inv" | PacketHelpers.listOfNSized(len, InventoryItem.codec)) }) ).as[InventoryData] } diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryItem.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryItem.scala index 5f1a2a902..7c5cf9dcd 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryItem.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/InventoryItem.scala @@ -5,13 +5,10 @@ import net.psforever.packet.Marshallable import scodec.Codec import scodec.codecs._ -case class InventoryItem(item : InternalSlot, - na : Option[Boolean] = None) +case class InventoryItem(item : InternalSlot) object InventoryItem extends Marshallable[InventoryItem] { implicit val codec : Codec[InventoryItem] = ( - "item" | InternalSlot.codec >>:~ { item => - conditional(item.obj.isDefined && item.obj.get.isInstanceOf[WeaponData], bool).hlist - } + "item" | InternalSlot.codec ).as[InventoryItem] } 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 index f5d3437ea..eb75953c5 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/Mold.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/Mold.scala @@ -4,8 +4,6 @@ package net.psforever.packet.game.objectcreate import scodec.DecodeResult import scodec.bits.BitVector -import scala.annotation.switch - case class Mold(objectClass : Int, data : BitVector) { private var obj : Option[ConstructorData] = Mold.decode(objectClass, data) @@ -31,33 +29,10 @@ object Mold { 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 { - (objClass : @switch) match { - case 0x79 => //avatars - outOpt = CharacterData.codec.decode(data).toOption - case 0x1C => //9mm - outOpt = AmmoBoxData.codec.decode(data).toOption - case 0x1D => //9mm ap - outOpt = AmmoBoxData.codec.decode(data).toOption - case 0x110 => //plasma - outOpt = AmmoBoxData.codec.decode(data).toOption - case 0x1C8 => //slot blocker? - outOpt = AmmoBoxData.codec.decode(data).toOption - case 0x21C => //forceblade (ammo) - outOpt = AmmoBoxData.codec.decode(data).toOption - case 0x46 => //beamer - outOpt = WeaponData.codec.decode(data).toOption - case 0x144 => //forceblade - outOpt = WeaponData.codec.decode(data).toOption - case 0x159 => //gauss - outOpt = WeaponData.codec.decode(data).toOption - case 0x34D => //suppressor - outOpt = WeaponData.codec.decode(data).toOption - case 0x2D8 => //rek - outOpt = REKData.codec.decode(data).toOption - case _ => - } + outOpt = codec.decode(data).toOption if(outOpt.isDefined) out = Some(outOpt.get.value.asInstanceOf[ConstructorData]) } @@ -74,31 +49,9 @@ object Mold { def encode(objClass : Int, obj : ConstructorData) : BitVector = { var out = BitVector.empty try { + val codec = ObjectClass.selectDataCodec(objClass) var outOpt : Option[BitVector] = None - (objClass : @switch) match { - case 0x1C => //9mm - outOpt = AmmoBoxData.codec.encode(obj.asInstanceOf[AmmoBoxData]).toOption - case 0x1D => //9mm ap - outOpt = AmmoBoxData.codec.encode(obj.asInstanceOf[AmmoBoxData]).toOption - case 0x110 => //plasma - outOpt = AmmoBoxData.codec.encode(obj.asInstanceOf[AmmoBoxData]).toOption - case 0x1C8 => //slot blocker? - outOpt = AmmoBoxData.codec.encode(obj.asInstanceOf[AmmoBoxData]).toOption - case 0x21C => //forceblade (ammo) - outOpt = AmmoBoxData.codec.encode(obj.asInstanceOf[AmmoBoxData]).toOption - case 0x46 => //beamer - outOpt = WeaponData.codec.encode(obj.asInstanceOf[WeaponData]).toOption - case 0x144 => //forceblade - outOpt = WeaponData.codec.encode(obj.asInstanceOf[WeaponData]).toOption - case 0x159 => //gauss - outOpt = WeaponData.codec.encode(obj.asInstanceOf[WeaponData]).toOption - case 0x34D => //suppressor - outOpt = WeaponData.codec.encode(obj.asInstanceOf[WeaponData]).toOption - case 0x2D8 => //rek - outOpt = REKData.codec.encode(obj.asInstanceOf[REKData]).toOption - case _ => - throw new ClassCastException("cannot find object code - "+objClass) - } + outOpt = codec.encode(obj.asInstanceOf[ConstructorData.genericPattern]).toOption if(outOpt.isDefined) out = outOpt.get } 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 new file mode 100644 index 000000000..9e4ae3ed6 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala @@ -0,0 +1,53 @@ +// Copyright (c) 2016 PSForever.net to present +package net.psforever.packet.game.objectcreate + +import scodec.Codec + +import scala.annotation.switch + +object ObjectClass { + //character + final val PLAYER = 0x79 + //ammunition + final val BULLETS_9MM = 0x1C + final val BULLETS_9MM_AP = 0x1D + final val ENERGY_CELL = 0x110 + final val FORCE_BLADE_AMMO = 0x21C + //weapons + final val BEAMER = 0x8C + final val FORCE_BLADE = 0x144 + final val GAUSS = 0x159 + final val SUPPRESSOR = 0x34D + //tools + final val REK = 0x2D8 + //unknown + final val SLOT_BLOCKER = 0x1C8 + + def selectDataCodec(objClass : Int) : Codec[ConstructorData.genericPattern] = { + (objClass : @switch) match { + case ObjectClass.PLAYER => CharacterData.genericCodec + case ObjectClass.BULLETS_9MM => AmmoBoxData.genericCodec + case ObjectClass.BULLETS_9MM_AP => AmmoBoxData.genericCodec + case ObjectClass.ENERGY_CELL => AmmoBoxData.genericCodec + case ObjectClass.FORCE_BLADE_AMMO => AmmoBoxData.genericCodec + case ObjectClass.BEAMER => WeaponData.genericCodec + case ObjectClass.FORCE_BLADE => WeaponData.genericCodec + case ObjectClass.GAUSS => WeaponData.genericCodec + case ObjectClass.SUPPRESSOR => WeaponData.genericCodec + case ObjectClass.REK => REKData.genericCodec + case ObjectClass.SLOT_BLOCKER => AmmoBoxData.genericCodec + case _ => RecoveredData.genericCodec + } + } + +// 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 b49786af2..ef396c832 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 @@ -6,7 +6,10 @@ import scodec.{Attempt, Codec, Err} import scodec.codecs._ import shapeless.{::, HNil} -case class REKData(unk : Int) extends ConstructorData +case class REKData(unk : Int + ) extends ConstructorData { + override def bsize : Long = 72L +} object REKData extends Marshallable[REKData] { implicit val codec : Codec[REKData] = ( @@ -29,4 +32,19 @@ object REKData extends Marshallable[REKData] { Attempt.successful(code :: 8 :: () :: 2 :: () :: 8 :: () :: HNil) } ).as[REKData] + + + + val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] ( + { + case x => + Attempt.successful(Some(x.asInstanceOf[ConstructorData])) + }, + { + case Some(x) => + Attempt.successful(x.asInstanceOf[REKData]) + case _ => + Attempt.failure(Err("")) + } + ) } 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 new file mode 100644 index 000000000..04cda576b --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/RecoveredData.scala @@ -0,0 +1,29 @@ +// 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/WeaponData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/WeaponData.scala index 2248bf2af..a1c21aca6 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 @@ -8,7 +8,9 @@ import scodec.codecs._ import shapeless.{::, HNil} case class WeaponData(unk : Int, - ammo : InternalSlot) extends ConstructorData + ammo : InternalSlot) extends ConstructorData { + override def bsize : Long = 59L + ammo.bsize +} object WeaponData extends Marshallable[WeaponData] { def apply(unk : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : WeaponData = @@ -34,4 +36,19 @@ object WeaponData extends Marshallable[WeaponData] { Attempt.successful(code :: 8 :: () :: 2 :: () :: 0x2C0 :: ammo :: HNil) } ).as[WeaponData] + + + + val genericCodec : Codec[ConstructorData.genericPattern] = codec.exmap[ConstructorData.genericPattern] ( + { + case x => + Attempt.successful(Some(x.asInstanceOf[ConstructorData])) + }, + { + case Some(x) => + Attempt.successful(x.asInstanceOf[WeaponData]) + case _ => + Attempt.failure(Err("")) + } + ) } diff --git a/common/src/test/scala/GamePacketTest.scala b/common/src/test/scala/GamePacketTest.scala index f3ebd4354..efdc3a93c 100644 --- a/common/src/test/scala/GamePacketTest.scala +++ b/common/src/test/scala/GamePacketTest.scala @@ -6,6 +6,7 @@ import net.psforever.packet._ import net.psforever.packet.game._ import net.psforever.packet.game.objectcreate._ import net.psforever.types._ +import scodec.Attempt import scodec.Attempt.Successful import scodec.bits._ @@ -147,36 +148,42 @@ 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" - val string_testchar = hex"18 570C0000 BC8 4B00 6C2D7 65535 CA16 0 00 01 34 40 00 0970 49006C006C006C004900490049006C006C006C0049006C0049006C006C0049006C006C006C0049006C006C004900 84 52 70 76 1E 80 80 00 00 00 00 00 3FFFC 0 00 00 00 20 00 00 0F F6 A7 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FD 90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 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 00 80 00 00 12 40 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8B 75 73 65 64 5F 62 65 61 6D 65 72 85 6D 61 70 31 33 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 01 23 02 60 04 04 40 00 00 10 00 06 02 08 14 D0 08 0C 80 00 02 00 02 6B 4E 00 82 88 00 00 02 00 00 C0 41 C0 9E 01 01 90 00 00 64 00 44 2A 00 10 91 00 00 00 40 00 18 08 38 94 40 20 32 00 00 00 80 19 05 48 02 17 20 00 00 08 00 70 29 80 43 64 00 00 32 00 0E 05 40 08 9C 80 00 06 40 01 C0 AA 01 19 90 00 00 C8 00 3A 15 80 28 72 00 00 19 00 04 0A B8 05 26 40 00 03 20 06 C2 58 00 A7 88 00 00 02 00 00 80 00 00" + val string_testchar = hex"18 570C0000 BC8 4B00 6C2D7 65535 CA16 0 00 01 34 40 00 0970 49006C006C006C004900490049006C006C006C0049006C0049006C006C0049006C006C006C0049006C006C004900 84 52 70 76 1E 80 80 00 00 00 00 00 3FFFC 0 00 00 00 20 00 00 0F F6 A7 03 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FD 90 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 01 90 01 90 00 64 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 00 80 00 00 12 40 78 70 65 5F 73 61 6E 63 74 75 61 72 79 5F 68 65 6C 70 90 78 70 65 5F 74 68 5F 66 69 72 65 6D 6F 64 65 73 8B 75 73 65 64 5F 62 65 61 6D 65 72 85 6D 61 70 31 33 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 23 02 60 04 04 40 00 00 10 00 06 02 08 14 D0 08 0C 80 00 02 00 02 6B 4E 00 82 88 00 00 02 00 00 C0 41 C0 9E 01 01 90 00 00 64 00 44 2A 00 10 91 00 00 00 40 00 18 08 38 94 40 20 32 00 00 00 80 19 05 48 02 17 20 00 00 08 00 70 29 80 43 64 00 00 32 00 0E 05 40 08 9C 80 00 06 40 01 C0 AA 01 19 90 00 00 C8 00 3A 15 80 28 72 00 00 19 00 04 0A B8 05 26 40 00 03 20 06 C2 58 00 A7 88 00 00 02 00 00 80 00 00" + val invTest = hex"01 01 23 02 60 04 04 40 00 00 10 00 06 02 08 14 D0 08 0C 80 00 02 00 00 00" + val invTestWep = hex"23 02 60 04 04 40 00 00 10 00 06 02 08 14 D0 08 0C 80 00 02 00 00 00" - "decode (2)" in { - PacketCoding.DecodePacket(packet2).require match { - case obj @ ObjectCreateMessage(len, cls, guid, parent, mold) => - len mustEqual 248 //60 + 188 - cls mustEqual 121 - guid mustEqual PlanetSideGUID(2497) - parent mustEqual None - mold.data mustEqual packet2Rest - mold.isDefined mustEqual false - case default => + "InventoryTest" in { + val intSlot = InternalSlot.codec.decode(invTestWep.toBitVector.drop(1)).toOption + intSlot.isDefined mustEqual true + + val invData = InventoryItem.codec.decode(invTestWep.toBitVector.drop(1)).toOption + invData.isDefined mustEqual true + + InventoryData.codec.decode(invTest.toBitVector.drop(7)).toOption match { + case Some(x) => + x.value.unk1 equals true + x.value.size mustEqual 1 + x.value.unk2 mustEqual false + //x.value.inv.head.item.objectClass mustEqual 0x8C + //x.value.inv.head.na mustEqual false + case _ => ko } } "decode (char)" in { PacketCoding.DecodePacket(string_testchar).require match { - case obj @ ObjectCreateMessage(len, cls, guid, parent, mold) => + case obj @ ObjectCreateMessage(len, cls, guid, parent, data) => len mustEqual 3159 cls mustEqual 0x79 guid mustEqual PlanetSideGUID(75) parent.isDefined mustEqual false - mold.isDefined mustEqual true + data.isDefined mustEqual true - val char = mold.get.asInstanceOf[CharacterData] + val char = data.get.asInstanceOf[CharacterData] char.pos.x mustEqual 3674.8438f char.pos.y mustEqual 2726.789f char.pos.z mustEqual 91.15625f @@ -268,24 +275,17 @@ class GamePacketTest extends Specification { } } - "encode (2)" in { - val msg = ObjectCreateMessage(0, 121, PlanetSideGUID(2497), None, Mold(121, packet2Rest)) - val pkt = PacketCoding.EncodePacket(msg).require.toByteVector - - pkt mustEqual packet2 - } - "encode (9mm)" in { - val obj = Mold(28, AmmoBoxData(50)) - val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 33)), obj) + val obj : ConstructorData = AmmoBoxData(50).asInstanceOf[ConstructorData] + val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 33)), Some(obj)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_9mm } "encode (gauss)" in { - val obj = Mold(345, WeaponData(4, 28, PlanetSideGUID(1286), 0, AmmoBoxData(30))) - val msg = ObjectCreateMessage(0, 345, PlanetSideGUID(1465), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 2)), obj) + val obj : ConstructorData = WeaponData(4, 28, PlanetSideGUID(1286), 0, AmmoBoxData(30)).asInstanceOf[ConstructorData] + val msg = ObjectCreateMessage(0, 345, PlanetSideGUID(1465), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 2)), Some(obj)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual string_gauss