replaced hamfisted greedy Mold functionality with seamless codec functionality, though that did require a significant re-write, and I might have re-introduced slow compilation; inventory hobbled intentionally

This commit is contained in:
FateJH 2016-12-05 23:30:52 -05:00
parent b6eed0dbbc
commit d66489a572
13 changed files with 399 additions and 172 deletions

View file

@ -1,9 +1,8 @@
// Copyright (c) 2016 PSForever.net to present // Copyright (c) 2016 PSForever.net to present
package net.psforever.packet.game 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 net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import scodec.bits._
import scodec.{Attempt, Codec, Err} import scodec.{Attempt, Codec, Err}
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil} import shapeless.{::, HNil}
@ -49,35 +48,37 @@ case class ObjectCreateMessageParent(guid : PlanetSideGUID,
* @param objectClass the code for the type of object being constructed * @param objectClass the code for the type of object being constructed
* @param guid the GUID this object will be assigned * @param guid the GUID this object will be assigned
* @param parentInfo if defined, the relationship between this object and another object (its parent) * @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; * @param data the data used to construct this type of object
* requires further object-specific processing
*/ */
case class ObjectCreateMessage(streamLength : Long, case class ObjectCreateMessage(streamLength : Long,
objectClass : Int, objectClass : Int,
guid : PlanetSideGUID, guid : PlanetSideGUID,
parentInfo : Option[ObjectCreateMessageParent], parentInfo : Option[ObjectCreateMessageParent],
mold : Mold) data : Option[ConstructorData])
extends PlanetSideGamePacket { extends PlanetSideGamePacket {
def opcode = GamePacketOpcode.ObjectCreateMessage def opcode = GamePacketOpcode.ObjectCreateMessage
def encode = ObjectCreateMessage.encode(this) def encode = ObjectCreateMessage.encode(this)
} }
object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] { 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. * Codec for formatting around the lack of parent data in the stream.
*/ */
val noParent : Codec[Pattern] = ( val noParent : Codec[Pattern] = (
("objectClass" | uintL(0xb)) :: //11u ("objectClass" | uintL(0xb)) >>:~ { cls => //11u
("guid" | PlanetSideGUID.codec) //16u ("guid" | PlanetSideGUID.codec) :: //16u
("data" | ObjectClass.selectDataCodec(cls))
}
).xmap[Pattern] ( ).xmap[Pattern] (
{ {
case cls :: guid :: HNil => case cls :: guid :: data :: HNil =>
cls :: guid :: None :: HNil cls :: guid :: None :: data :: HNil
}, },
{ {
case cls :: guid :: None :: HNil => case cls :: guid :: None :: data :: HNil =>
cls :: guid :: HNil cls :: guid :: data :: HNil
} }
) )
@ -86,17 +87,19 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
*/ */
val parent : Codec[Pattern] = ( val parent : Codec[Pattern] = (
("parentGuid" | PlanetSideGUID.codec) :: //16u ("parentGuid" | PlanetSideGUID.codec) :: //16u
("objectClass" | uintL(0xb)) :: //11u (("objectClass" | uintL(0xb)) >>:~ { cls => //11u
("guid" | PlanetSideGUID.codec) :: //16u ("guid" | PlanetSideGUID.codec) :: //16u
("parentSlotIndex" | PacketHelpers.encodedStringSize) //8u or 16u ("parentSlotIndex" | PacketHelpers.encodedStringSize) :: //8u or 16u
("data" | ObjectClass.selectDataCodec(cls))
})
).xmap[Pattern] ( ).xmap[Pattern] (
{ {
case pguid :: cls :: guid :: slot :: HNil => case pguid :: cls :: guid :: slot :: data :: HNil =>
cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: data :: HNil
}, },
{ {
case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: HNil => case cls :: guid :: Some(ObjectCreateMessageParent(pguid, slot)) :: data :: HNil =>
pguid :: cls :: guid :: slot :: 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 * @param data the data length is indeterminate until it is read
* @return the total length of the stream in bits * @return the total length of the stream in bits
*/ */
private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : BitVector) : Long = { private def streamLen(parentInfo : Option[ObjectCreateMessageParent], data : Option[ConstructorData]) : Long = {
//known length //msg length
val first : Long = if(parentInfo.isDefined) { val first : Long = if(parentInfo.isDefined) { //(32u + 1u + 11u + 16u) ?+ (16u + (8u | 16u))
if(parentInfo.get.slot > 127) 92L else 84L //60u + 16u + (8u or 16u) if(parentInfo.get.slot > 127) 92L else 84L
} }
else { else {
60L 60L
} }
//variant length //data length
var second : Long = data.size var second : Long = if(data.isDefined) data.get.bsize else 0L
val secondMod4 : Long = second % 4L val secondMod4 : Long = second % 4L
if(secondMod4 > 0L) { //pad to include last whole nibble if(secondMod4 > 0L) { //pad to include last whole nibble
second += 4L - secondMod4 second += 4L - secondMod4
@ -133,34 +136,142 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
implicit val codec : Codec[ObjectCreateMessage] = ( implicit val codec : Codec[ObjectCreateMessage] = (
("streamLength" | uint32L) :: ("streamLength" | uint32L) ::
(either(bool, parent, noParent).exmap[Pattern] ( either(bool, parent, noParent).exmap[Pattern] (
{ {
case Left(a :: b :: Some(c) :: HNil) => case Left(a :: b :: Some(c) :: d :: HNil) =>
Attempt.successful(a :: b :: Some(c) :: HNil) //true, _, _, Some(c) Attempt.successful(a :: b :: Some(c) :: d :: HNil) //true, _, _, Some(c)
case Right(a :: b :: None :: HNil) => case Right(a :: b :: None :: d :: HNil) =>
Attempt.successful(a :: b :: None :: HNil) //false, _, _, None Attempt.successful(a :: b :: None :: d :: HNil) //false, _, _, None
// failure cases // failure cases
case Left(a :: b :: None :: HNil) => case Left(a :: b :: None :: _ :: HNil) =>
Attempt.failure(Err("missing parent structure")) //true, _, _, None 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) Attempt.failure(Err("unexpected parent structure")) //false, _, _, Some(c)
}, },
{ {
case a :: b :: Some(c) :: HNil => case a :: b :: Some(c) :: d :: HNil =>
Attempt.successful(Left(a :: b :: Some(c) :: HNil)) Attempt.successful(Left(a :: b :: Some(c) :: d :: HNil))
case a :: b :: None :: HNil => case a :: b :: None :: d :: HNil =>
Attempt.successful(Right(a :: b :: None :: HNil)) Attempt.successful(Right(a :: b :: None :: d :: HNil))
} }
) :+ )
("data" | bits) ) ).xmap[outPattern] (
).xmap[ObjectCreateMessage] (
{ {
case len :: cls :: guid :: info :: data :: HNil => case len :: cls :: guid :: par :: data :: HNil =>
ObjectCreateMessage(len, cls, guid, info, Mold(cls, data)) len :: cls :: guid :: par :: data :: HNil
}, },
{ {
case ObjectCreateMessage(_, cls, guid, info, mold) => case _ :: cls :: guid :: par :: data :: HNil =>
streamLen(info, mold.data) :: cls :: guid :: info :: mold.data :: HNil streamLen(par, data) :: cls :: guid :: par :: data :: HNil
} }
).as[ObjectCreateMessage] ).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]
//}

View file

@ -6,7 +6,10 @@ import scodec.{Attempt, Codec, Err}
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil} 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] { object AmmoBoxData extends Marshallable[AmmoBoxData] {
implicit val codec : Codec[AmmoBoxData] = ( implicit val codec : Codec[AmmoBoxData] = (
@ -24,5 +27,18 @@ object AmmoBoxData extends Marshallable[AmmoBoxData] {
case AmmoBoxData(mag) => case AmmoBoxData(mag) =>
Attempt.successful(0xC8 :: () :: mag :: HNil) 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(""))
}
)
} }

View file

@ -3,7 +3,7 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.{Marshallable, PacketHelpers} import net.psforever.packet.{Marshallable, PacketHelpers}
import net.psforever.types.Vector3 import net.psforever.types.Vector3
import scodec.Codec import scodec.{Attempt, Codec, Err}
import scodec.codecs._ import scodec.codecs._
case class CharacterData(pos : Vector3, case class CharacterData(pos : Vector3,
@ -41,9 +41,36 @@ case class CharacterData(pos : Vector3,
firstTimeEvent_list : List[String], firstTimeEvent_list : List[String],
tutorial_list : List[String], tutorial_list : List[String],
inventory : InventoryData 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] { 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] = ( implicit val codec : Codec[CharacterData] = (
("pos" | Vector3.codec_pos) :: ("pos" | Vector3.codec_pos) ::
ignore(16) :: ignore(16) ::
@ -56,7 +83,7 @@ object CharacterData extends Marshallable[CharacterData] {
("exosuit" | uintL(3)) :: ("exosuit" | uintL(3)) ::
ignore(2) :: ignore(2) ::
("sex" | uintL(2)) :: ("sex" | uintL(2)) ::
("face1" | uint8L) :: ("face1" | uint4L) ::
("face2" | uint4L) :: ("face2" | uint4L) ::
("voice" | uintL(3)) :: ("voice" | uintL(3)) ::
ignore(22) :: ignore(22) ::
@ -98,4 +125,17 @@ object CharacterData extends Marshallable[CharacterData] {
("inventory" | InventoryData.codec) ("inventory" | InventoryData.codec)
}) })
).as[CharacterData] ).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(""))
}
)
} }

View file

@ -1,4 +1,10 @@
// Copyright (c) 2016 PSForever.net to present // Copyright (c) 2016 PSForever.net to present
package net.psforever.packet.game.objectcreate package net.psforever.packet.game.objectcreate
abstract class ConstructorData abstract class ConstructorData() {
def bsize : Long = 0L
}
object ConstructorData {
type genericPattern = Option[ConstructorData]
}

View file

@ -3,8 +3,7 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.{Marshallable, PacketHelpers} import net.psforever.packet.{Marshallable, PacketHelpers}
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import scodec.{Attempt, Codec, Err} import scodec.Codec
import scodec.bits.BitVector
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil} import shapeless.{::, HNil}
@ -19,40 +18,23 @@ import shapeless.{::, HNil}
case class InternalSlot(objectClass : Int, case class InternalSlot(objectClass : Int,
guid : PlanetSideGUID, guid : PlanetSideGUID,
parentSlot : Int, 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] { 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] = ( implicit val codec : Codec[InternalSlot] = (
ignore(1) :: //TODO determine what this bit does ignore(1) :: //TODO determine what this bit does
("objectClass" | uintL(11)) :: (("objectClass" | uintL(11)) >>:~ { obj_cls =>
("guid" | PlanetSideGUID.codec) :: ("guid" | PlanetSideGUID.codec) ::
("parentSlot" | PacketHelpers.encodedStringSize) :: ("parentSlot" | PacketHelpers.encodedStringSize) ::
bits ("obj" | ObjectClass.selectDataCodec(obj_cls))
).exmap[objPattern] ( })
{ ).as[InternalSlot]
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]
} }

View file

@ -3,19 +3,24 @@ package net.psforever.packet.game.objectcreate
import net.psforever.packet.{Marshallable, PacketHelpers} import net.psforever.packet.{Marshallable, PacketHelpers}
import scodec.Codec import scodec.Codec
import scodec.bits.BitVector
import scodec.codecs._ import scodec.codecs._
case class InventoryData(unk1 : Boolean, case class InventoryData(unk1 : Boolean,
size : Int, size : Int,
unk2 : Boolean, unk2 : Boolean){//,
inv : List[InventoryItem]) //inv : List[InventoryItem]) {
def bsize : Long = {
10L
}
}
object InventoryData extends Marshallable[InventoryData] { object InventoryData extends Marshallable[InventoryData] {
implicit val codec : Codec[InventoryData] = ( implicit val codec : Codec[InventoryData] = (
("unk1" | bool) :: ("unk1" | bool) ::
(("size" | uint8L) >>:~ { len => (("size" | uint8L) >>:~ { len =>
("unk2" | bool) :: ("unk2" | bool).hlist// ::
("inv" | PacketHelpers.listOfNSized(len, InventoryItem.codec)) //("inv" | PacketHelpers.listOfNSized(len, InventoryItem.codec))
}) })
).as[InventoryData] ).as[InventoryData]
} }

View file

@ -5,13 +5,10 @@ import net.psforever.packet.Marshallable
import scodec.Codec import scodec.Codec
import scodec.codecs._ import scodec.codecs._
case class InventoryItem(item : InternalSlot, case class InventoryItem(item : InternalSlot)
na : Option[Boolean] = None)
object InventoryItem extends Marshallable[InventoryItem] { object InventoryItem extends Marshallable[InventoryItem] {
implicit val codec : Codec[InventoryItem] = ( implicit val codec : Codec[InventoryItem] = (
"item" | InternalSlot.codec >>:~ { item => "item" | InternalSlot.codec
conditional(item.obj.isDefined && item.obj.get.isInstanceOf[WeaponData], bool).hlist
}
).as[InventoryItem] ).as[InventoryItem]
} }

View file

@ -4,8 +4,6 @@ package net.psforever.packet.game.objectcreate
import scodec.DecodeResult import scodec.DecodeResult
import scodec.bits.BitVector import scodec.bits.BitVector
import scala.annotation.switch
case class Mold(objectClass : Int, case class Mold(objectClass : Int,
data : BitVector) { data : BitVector) {
private var obj : Option[ConstructorData] = Mold.decode(objectClass, data) private var obj : Option[ConstructorData] = Mold.decode(objectClass, data)
@ -31,33 +29,10 @@ object Mold {
def decode(objClass : Int, data : BitVector) : Option[ConstructorData] = { def decode(objClass : Int, data : BitVector) : Option[ConstructorData] = {
var out : Option[ConstructorData] = None var out : Option[ConstructorData] = None
if(!data.isEmpty) { if(!data.isEmpty) {
val codec = ObjectClass.selectDataCodec(objClass)
var outOpt : Option[DecodeResult[_]] = None var outOpt : Option[DecodeResult[_]] = None
try { try {
(objClass : @switch) match { outOpt = codec.decode(data).toOption
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 _ =>
}
if(outOpt.isDefined) if(outOpt.isDefined)
out = Some(outOpt.get.value.asInstanceOf[ConstructorData]) out = Some(outOpt.get.value.asInstanceOf[ConstructorData])
} }
@ -74,31 +49,9 @@ object Mold {
def encode(objClass : Int, obj : ConstructorData) : BitVector = { def encode(objClass : Int, obj : ConstructorData) : BitVector = {
var out = BitVector.empty var out = BitVector.empty
try { try {
val codec = ObjectClass.selectDataCodec(objClass)
var outOpt : Option[BitVector] = None var outOpt : Option[BitVector] = None
(objClass : @switch) match { outOpt = codec.encode(obj.asInstanceOf[ConstructorData.genericPattern]).toOption
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)
}
if(outOpt.isDefined) if(outOpt.isDefined)
out = outOpt.get out = outOpt.get
} }

View file

@ -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"))
// }
// )
}

View file

@ -6,7 +6,10 @@ import scodec.{Attempt, Codec, Err}
import scodec.codecs._ import scodec.codecs._
import shapeless.{::, HNil} 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] { object REKData extends Marshallable[REKData] {
implicit val codec : Codec[REKData] = ( implicit val codec : Codec[REKData] = (
@ -29,4 +32,19 @@ object REKData extends Marshallable[REKData] {
Attempt.successful(code :: 8 :: () :: 2 :: () :: 8 :: () :: HNil) Attempt.successful(code :: 8 :: () :: 2 :: () :: 8 :: () :: HNil)
} }
).as[REKData] ).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(""))
}
)
} }

View file

@ -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"))
}
)
}

View file

@ -8,7 +8,9 @@ import scodec.codecs._
import shapeless.{::, HNil} import shapeless.{::, HNil}
case class WeaponData(unk : Int, 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] { object WeaponData extends Marshallable[WeaponData] {
def apply(unk : Int, cls : Int, guid : PlanetSideGUID, parentSlot : Int, ammo : AmmoBoxData) : 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) Attempt.successful(code :: 8 :: () :: 2 :: () :: 0x2C0 :: ammo :: HNil)
} }
).as[WeaponData] ).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(""))
}
)
} }

View file

@ -6,6 +6,7 @@ import net.psforever.packet._
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate._ import net.psforever.packet.game.objectcreate._
import net.psforever.types._ import net.psforever.types._
import scodec.Attempt
import scodec.Attempt.Successful import scodec.Attempt.Successful
import scodec.bits._ import scodec.bits._
@ -147,36 +148,42 @@ class GamePacketTest extends Specification {
"ObjectCreateMessage" should { "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 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 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 packet2Rest = packet2.bits.drop(8 + 32 + 1 + 11 + 16)
val string_9mm = hex"18 7C000000 2580 0E0 0005 A1 C8000064000" 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_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 { "InventoryTest" in {
PacketCoding.DecodePacket(packet2).require match { val intSlot = InternalSlot.codec.decode(invTestWep.toBitVector.drop(1)).toOption
case obj @ ObjectCreateMessage(len, cls, guid, parent, mold) => intSlot.isDefined mustEqual true
len mustEqual 248 //60 + 188
cls mustEqual 121 val invData = InventoryItem.codec.decode(invTestWep.toBitVector.drop(1)).toOption
guid mustEqual PlanetSideGUID(2497) invData.isDefined mustEqual true
parent mustEqual None
mold.data mustEqual packet2Rest InventoryData.codec.decode(invTest.toBitVector.drop(7)).toOption match {
mold.isDefined mustEqual false case Some(x) =>
case default => 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 ko
} }
} }
"decode (char)" in { "decode (char)" in {
PacketCoding.DecodePacket(string_testchar).require match { 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 len mustEqual 3159
cls mustEqual 0x79 cls mustEqual 0x79
guid mustEqual PlanetSideGUID(75) guid mustEqual PlanetSideGUID(75)
parent.isDefined mustEqual false 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.x mustEqual 3674.8438f
char.pos.y mustEqual 2726.789f char.pos.y mustEqual 2726.789f
char.pos.z mustEqual 91.15625f 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 { "encode (9mm)" in {
val obj = Mold(28, AmmoBoxData(50)) val obj : ConstructorData = AmmoBoxData(50).asInstanceOf[ConstructorData]
val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 33)), obj) val msg = ObjectCreateMessage(0, 28, PlanetSideGUID(1280), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 33)), Some(obj))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_9mm pkt mustEqual string_9mm
} }
"encode (gauss)" in { "encode (gauss)" in {
val obj = Mold(345, WeaponData(4, 28, PlanetSideGUID(1286), 0, AmmoBoxData(30))) 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)), obj) val msg = ObjectCreateMessage(0, 345, PlanetSideGUID(1465), Some(ObjectCreateMessageParent(PlanetSideGUID(75), 2)), Some(obj))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_gauss pkt mustEqual string_gauss