mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
combining hamfisted approach and hlist approach to better manage failure cases
This commit is contained in:
parent
d66489a572
commit
9adb077d8c
|
|
@ -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.)<br>
|
||||
* <br>
|
||||
* Knowing the object's class is essential for parsing the specific information passed by the `data` parameter.<br>
|
||||
* <br>
|
||||
* Exploration:<br>
|
||||
* 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.<br>
|
||||
* Take bit data and transform it into an object that expresses the important information of a game piece.<br>
|
||||
* <br>
|
||||
* 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.<br>
|
||||
* <br>
|
||||
* 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.<br>
|
||||
* <br>
|
||||
* 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]
|
||||
//}
|
||||
}
|
||||
|
|
@ -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] {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
package net.psforever.packet.game.objectcreate
|
||||
|
||||
abstract class ConstructorData() {
|
||||
def bsize : Long = 0L
|
||||
def bitsize : Long = 0L
|
||||
}
|
||||
|
||||
object ConstructorData {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
})
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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"))
|
||||
// }
|
||||
// )
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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] {
|
||||
|
|
|
|||
|
|
@ -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"))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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] = (
|
||||
|
|
|
|||
|
|
@ -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] {
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Reference in a new issue