in the middle of character decode test implementation

This commit is contained in:
FateJH 2016-12-02 15:16:56 -05:00
parent ee87e1c6ec
commit b0b5a7005b
2 changed files with 184 additions and 140 deletions

View file

@ -1,7 +1,8 @@
package net.psforever.packet.game package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
//import net.psforever.types.Vector3 import scodec.DecodeResult
import net.psforever.types.Vector3
import scodec.bits._ import scodec.bits._
import scodec.{Attempt, Codec, Err} import scodec.{Attempt, Codec, Err}
import scodec.codecs._ import scodec.codecs._
@ -19,25 +20,25 @@ object AmmoBoxData extends Marshallable[AmmoBoxData] {
ignore(15) :: ignore(15) ::
("magazine" | uint16L) ("magazine" | uint16L)
).exmap[AmmoBoxData] ( ).exmap[AmmoBoxData] (
{ {
case 0xC8 :: _ :: mag :: HNil => case 0xC8 :: _ :: mag :: HNil =>
Attempt.successful(AmmoBoxData(mag)) Attempt.successful(AmmoBoxData(mag))
case x :: _ :: _ :: HNil => case x :: _ :: _ :: HNil =>
Attempt.failure(Err("code wrong - looking for 200, found "+x)) Attempt.failure(Err("looking for 200, found "+x))
}, },
{ {
case AmmoBoxData(mag) => case AmmoBoxData(mag) =>
Attempt.successful(0xC8 :: () :: mag :: HNil) Attempt.successful(0xC8 :: () :: mag :: HNil)
} }
).as[AmmoBoxData] ).as[AmmoBoxData]
} }
case class WeaponData(unk : Int, case class WeaponData(unk : Int,
ammo : InternalMold) extends ConstructorData ammo : InternalSlot) extends ConstructorData
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 =
new WeaponData(unk, InternalMold(cls, guid, parentSlot, Some(ammo))) new WeaponData(unk, InternalSlot(cls, guid, parentSlot, Some(ammo)))
implicit val codec : Codec[WeaponData] = ( implicit val codec : Codec[WeaponData] = (
("unk" | uint4L) :: ("unk" | uint4L) ::
@ -46,13 +47,13 @@ object WeaponData extends Marshallable[WeaponData] {
uint4L :: uint4L ::
ignore(16) :: ignore(16) ::
uintL(11) :: uintL(11) ::
InternalMold.codec ("ammo" | InternalSlot.codec)
).exmap[WeaponData] ( ).exmap[WeaponData] (
{ {
case code :: 8 :: _ :: 2 :: _ :: 0x2C0 :: ammo :: HNil => case code :: 8 :: _ :: 2 :: _ :: 0x2C0 :: ammo :: HNil =>
Attempt.successful(WeaponData(code, ammo)) Attempt.successful(WeaponData(code, ammo))
case _ :: x :: _ :: y :: _ :: z :: _ :: HNil => case _ :: x :: _ :: y :: _ :: z :: _ :: HNil =>
Attempt.failure(Err("looking for 8-2-704 pattern, found %d-%d-%d".format(x,y,z))) Attempt.failure(Err("looking for 8-2-704 pattern, found %d-%d-%d".format(x,y,z))) //TODO I actually don't know what of this is actually important
}, },
{ {
case WeaponData(code, ammo) => case WeaponData(code, ammo) =>
@ -61,26 +62,39 @@ object WeaponData extends Marshallable[WeaponData] {
).as[WeaponData] ).as[WeaponData]
} }
//case class CharacterData(pos : Vector3, case class RibbonBars(upper : Int, //0xFFFF means no merit (for all ...)
// obj_yaw : Int, middle : Int,
// faction : Int, lower : Int,
// bops : Boolean, tos : Int)
// name : String,
// exosuit : Int, object RibbonBars extends Marshallable[RibbonBars] {
// sex : Int, implicit val codec : Codec[RibbonBars] = (
// face1 : Int, ("upper" | uint16L) ::
// face2 : Int, ("middle" | uint16L) ::
// voice : Int, ("lower" | uint16L) ::
// unk1 : Int, //0x8080 ("tos" | uint16L)
// unk2 : Int, //0xFFFF or 0x0 ).as[RibbonBars]
// unk3 : Int, //2 }
// anchor : Boolean,
// viewPitch : Int, case class CharacterData(pos : Vector3,
// viewYaw : Int, objYaw : Int,
// upperMerit : Int, //0xFFFF means no merit (for all ...) faction : Int,
// middleMerit : Int, bops : Boolean,
// lowerMerit : Int, name : String,
// termOfServiceMerit : Int, exosuit : Int,
sex : Int,
face1 : Int,
face2 : Int,
voice : Int,
unk1 : Int, //0x8080
unk2 : Int, //0xFFFF or 0x0
unk3 : Int, //2
viewPitch : Int,
viewYaw : Int,
upperMerit : Int, //0xFFFF means no merit (for all ...)
middleMerit : Int,
lowerMerit : Int,
termOfServiceMerit : Int,
// healthMax : Int, // healthMax : Int,
// health : Int, // health : Int,
// armor : Int, // armor : Int,
@ -96,72 +110,62 @@ object WeaponData extends Marshallable[WeaponData] {
// unk11 : Int, //134 // unk11 : Int, //134
// unk12 : Int, //199 // unk12 : Int, //199
// firstTimeEvent_length : Long, // firstTimeEvent_length : Long,
// firstEntry : String, // firstEntry : Option[String],
// firstTimEvent_list : List[String], // firstTimeEvent_list : List[String],
// tutorial_list : List[String], // tutorial_list : List[String],
// inventory : BitVector inventory : BitVector
// ) extends ConstructorData ) extends ConstructorData
//
//object CharacterData extends Marshallable[CharacterData] { object CharacterData extends Marshallable[CharacterData] {
// //all ignore()s are intentional; please do not mess with them val ribbonBars : Codec[RibbonBars] = (
// implicit val codec : Codec[CharacterData] = ( ("upper" | uint16L) ::
// ("pos" | Vector3.codec_pos) :: ("middle" | uint16L) ::
// ignore(16) :: ("lower" | uint16L) ::
// ("obj_yaw" | uint8L) :: ("tos" | uint16L)
// ignore(1) :: ).as[RibbonBars]
// ("faction" | uintL(2)) ::
// ("bops" | bool) :: implicit val codec : Codec[CharacterData] = (
// ignore(4) :: ("pos" | Vector3.codec_pos) ::
// ignore(16) :: ignore(16) ::
// ("name" | PacketHelpers.encodedWideStringAligned(4)) :: ("objYaw" | uint8L) ::
// ("exosuit" | uintL(3)) :: ignore(1) ::
// ignore(1) :: ("faction" | uintL(2)) ::
// ignore(1) :: ("bops" | bool) ::
// ("sex" | uintL(2)) :: ignore(20) ::
// ("face1" | uint4L) :: ("name" | PacketHelpers.encodedWideStringAligned(4)) ::
// ("face2" | uint4L) :: ("exosuit" | uintL(3)) ::
// ("voice" | uintL(3)) :: ignore(2) ::
// ignore(2) :: ("sex" | uintL(2)) ::
// ignore(4) :: ("face1" | uint4L) ::
// ignore(16) :: ("face2" | uint4L) ::
// ("unk1" | uint16L) :: ("voice" | uintL(3)) ::
// ignore(40) :: ignore(22) ::
// ignore(2) :: ("unk1" | uint16L) ::
// ("unk2" | uint16L) :: ignore(42) ::
// ignore(2) :: ("unk2" | uint16L) ::
// ignore(28) :: ignore(30) ::
// ("unk3" | uintL(4)) :: ("unk3" | uintL(4)) ::
// ignore(4) :: ignore(24) ::
// ignore(16) :: ("viewPitch" | uint8L) ::
// ignore(2) :: ("viewYaw" | uint8L) ::
// ("anchor" | bool) :: ignore(10) ::
// ignore(1) :: ("upperMerit" | uint16L) ::
// ("viewPitch" | uint8L) :: ("middleMerit" | uint16L) ::
// ("viewYaw" | uint8L) :: ("lowerMerit" | uint16L) ::
// ignore(4) :: ("termOfServiceMerit" | uint16L) ::
// ignore(4) :: // ignore(160) ::
// ignore(2) ::
// ("upperMerit" | uint16L) ::
// ("middleMerit" | uint16L) ::
// ("lowerMerit" | uint16L) ::
// ("termOfServiceMerit" | uint16L) ::
// ignore(2) ::
// ignore(156) ::
// ignore(2) ::
// ("healthMax" | uint16L) :: // ("healthMax" | uint16L) ::
// ("health" | uint16L) :: // ("health" | uint16L) ::
// ignore(1) :: // ignore(1) ::
// ("armor" | uint16L) :: // ("armor" | uint16L) ::
// ignore(1) :: // ignore(9) ::
// ignore(8) ::
// ("unk4" | uint8L) :: // ("unk4" | uint8L) ::
// ignore(8) :: // ignore(8) ::
// ("unk5" | uint4L) :: // ("unk5" | uint4L) ::
// ("unk6" | uintL(3)) :: // ("unk6" | uintL(3)) ::
// ("staminaMax" | uint16L) :: // ("staminaMax" | uint16L) ::
// ("stamina" | uint16L) :: // ("stamina" | uint16L) ::
// ignore(148) :: // ignore(152) ::
// ignore(4) ::
// ("unk7" | uint16L) :: // ("unk7" | uint16L) ::
// ("unk8" | uint8L) :: // ("unk8" | uint8L) ::
// ("unk9" | uint8L) :: // ("unk9" | uint8L) ::
@ -170,49 +174,49 @@ object WeaponData extends Marshallable[WeaponData] {
// ("unk12" | uintL(12)) :: // ("unk12" | uintL(12)) ::
// ignore(3) :: // ignore(3) ::
// (("firstTimeEvent_length" | uint32L) >>:~ { len => // (("firstTimeEvent_length" | uint32L) >>:~ { len =>
// ("firstEntry" | PacketHelpers.encodedStringAligned(5)) :: // conditional(len > 0, "firstEntry" | PacketHelpers.encodedStringAligned(5)) ::
// ("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) // ("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString)) ::
// }) :: // ("tutorial_list" | PacketHelpers.listOfNAligned(uint32L, 0, PacketHelpers.encodedString)) ::
// ("tutorial_list" | PacketHelpers.listOfNAligned(uint32L, 0, PacketHelpers.encodedString)) :: // ignore(207) ::
// ignore(204) :: ("inventory" | bits)
// ignore(3) :: // })
// ("inventory" | bits) ).as[CharacterData]
// ).as[CharacterData] }
//}
/** /**
* na * The same kind of data as required for a formal ObjectCreateMessage but with a required and implicit parent relationship.
* Data preceding this entry will define the existence of the parent.
* @param objectClass na * @param objectClass na
* @param guid na * @param guid na
* @param parentSlot na * @param parentSlot na
* @param obj na * @param obj na
*/ */
case class InternalMold(objectClass : Int, case class InternalSlot(objectClass : Int,
guid : PlanetSideGUID, guid : PlanetSideGUID,
parentSlot : Int, parentSlot : Int,
obj : Option[ConstructorData]) obj : Option[ConstructorData])
object InternalMold extends Marshallable[InternalMold] { object InternalSlot extends Marshallable[InternalSlot] {
type objPattern = Int :: PlanetSideGUID :: Int :: Option[ConstructorData] :: HNil type objPattern = Int :: PlanetSideGUID :: Int :: Option[ConstructorData] :: HNil
implicit val codec : Codec[InternalMold] = ( 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)) ::
("guid" | PlanetSideGUID.codec) :: ("guid" | PlanetSideGUID.codec) ::
("parentSlot" | PacketHelpers.encodedStringSize) :: ("parentSlot" | PacketHelpers.encodedStringSize) ::
("data" | bits) bits
).exmap[objPattern] ( ).exmap[objPattern] (
{ {
case _ :: cls :: guid :: slot :: data :: HNil => case _ :: cls :: guid :: slot :: data :: HNil =>
Attempt.successful(cls :: guid :: slot :: Mold.selectMold(cls, data) :: HNil) Attempt.successful(cls :: guid :: slot :: Mold.decode(cls, data) :: HNil)
}, },
{ {
case cls :: guid :: slot :: None :: HNil => case cls :: guid :: slot :: None :: HNil =>
Attempt.failure(Err("no constuctor data could be found")) Attempt.failure(Err("no constuctor data could be found"))
case cls :: guid :: slot :: mold :: HNil => case cls :: guid :: slot :: mold :: HNil =>
Attempt.successful(() :: cls :: guid :: slot :: Mold.serialize(cls, mold.get) :: HNil) Attempt.successful(() :: cls :: guid :: slot :: Mold.encode(cls, mold.get) :: HNil)
} }
).exmap[objPattern] ( ).exmap[objPattern] (
{ {
case cls :: guid :: slot :: None :: HNil => case cls :: guid :: slot :: None :: HNil =>
Attempt.failure(Err("no decoded constructor data")) Attempt.failure(Err("no decoded constructor data"))
@ -225,12 +229,12 @@ object InternalMold extends Marshallable[InternalMold] {
case cls :: guid :: slot :: data :: HNil => case cls :: guid :: slot :: data :: HNil =>
Attempt.successful(cls :: guid :: slot :: data :: HNil) Attempt.successful(cls :: guid :: slot :: data :: HNil)
} }
).as[InternalMold] ).as[InternalSlot]
} }
case class Mold(objectClass : Int, case class Mold(objectClass : Int,
data : BitVector) { data : BitVector) {
private var obj : Option[ConstructorData] = Mold.selectMold(objectClass, data) private var obj : Option[ConstructorData] = Mold.decode(objectClass, data)
def isDefined : Boolean = this.obj.isDefined def isDefined : Boolean = this.obj.isDefined
@ -248,55 +252,59 @@ case class Mold(objectClass : Int,
object Mold { object Mold {
def apply(objectClass : Int, obj : ConstructorData) : Mold = def apply(objectClass : Int, obj : ConstructorData) : Mold =
new Mold( objectClass, Mold.serialize(objectClass, obj) ) new Mold( objectClass, Mold.encode(objectClass, obj) )
def selectMold(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) {
(objClass : @switch) match { var outOpt : Option[DecodeResult[_]] = None
case 0x1C => //9mm try {
val opt = AmmoBoxData.codec.decode(data).toOption (objClass : @switch) match {
if(opt.isDefined) case 0x79 => //avatars
out = Some(opt.get.value) outOpt = CharacterData.codec.decode(data).toOption
case 0x46 => //beamer case 0x1C => //9mm
val opt = WeaponData.codec.decode(data).toOption outOpt = AmmoBoxData.codec.decode(data).toOption
if(opt.isDefined) case 0x46 => //beamer
out = Some(opt.get.value) outOpt = WeaponData.codec.decode(data).toOption
case 0x159 => //gauss case 0x159 => //gauss
val opt = WeaponData.codec.decode(data).toOption outOpt = WeaponData.codec.decode(data).toOption
if(opt.isDefined) case _ =>
out = Some(opt.get.value) }
case _ => 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 out
} }
def serialize(objClass : Int, obj : ConstructorData) : BitVector = { def encode(objClass : Int, obj : ConstructorData) : BitVector = {
var out = BitVector.empty var out = BitVector.empty
try { try {
var outOpt : Option[BitVector] = None
(objClass : @switch) match { (objClass : @switch) match {
case 0x1C => //9mm case 0x1C => //9mm
val opt = AmmoBoxData.codec.encode(obj.asInstanceOf[AmmoBoxData]).toOption outOpt = AmmoBoxData.codec.encode(obj.asInstanceOf[AmmoBoxData]).toOption
if(opt.isDefined)
out = opt.get
case 0x46 => //beamer case 0x46 => //beamer
val opt = WeaponData.codec.encode(obj.asInstanceOf[WeaponData]).toOption outOpt = WeaponData.codec.encode(obj.asInstanceOf[WeaponData]).toOption
if(opt.isDefined)
out = opt.get
case 0x159 => //gauss case 0x159 => //gauss
val opt = WeaponData.codec.encode(obj.asInstanceOf[WeaponData]).toOption outOpt = WeaponData.codec.encode(obj.asInstanceOf[WeaponData]).toOption
if(opt.isDefined)
out = opt.get
case _ => case _ =>
throw new ClassCastException("cannot find object code - "+objClass) throw new ClassCastException("cannot find object code - "+objClass)
} }
if(outOpt.isDefined)
out = outOpt.get
} }
catch { catch {
case ex : ClassCastException => case ex : ClassCastException =>
//TODO generate and log wrong class error message //TODO generate and log wrong class error message
case ex : Exception => case ex : Exception =>
//TODO generic error //TODO generic error
} }
out out
} }
@ -453,7 +461,6 @@ object ObjectCreateMessage extends Marshallable[ObjectCreateMessage] {
ObjectCreateMessage(len, cls, guid, info, Mold(cls, data)) ObjectCreateMessage(len, cls, guid, info, Mold(cls, data))
}, },
{ {
//the user should not have to manually supply a proper stream length, that's a restrictive requirement
case ObjectCreateMessage(_, cls, guid, info, mold) => case ObjectCreateMessage(_, cls, guid, info, mold) =>
streamLen(info, mold.data) :: cls :: guid :: info :: mold.data :: HNil streamLen(info, mold.data) :: cls :: guid :: info :: mold.data :: HNil
} }

View file

@ -150,6 +150,7 @@ class GamePacketTest extends Specification {
val packet2Rest = packet2.bits.drop(8 + 32 + 1 + 11 + 16) 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 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"
"decode (2)" in { "decode (2)" in {
PacketCoding.DecodePacket(packet2).require match { PacketCoding.DecodePacket(packet2).require match {
@ -165,6 +166,42 @@ class GamePacketTest extends Specification {
} }
} }
"decode (char)" in {
PacketCoding.DecodePacket(string_testchar).require match {
case obj @ ObjectCreateMessage(len, cls, guid, parent, mold) =>
len mustEqual 3159
cls mustEqual 0x79
guid mustEqual PlanetSideGUID(75)
parent.isDefined mustEqual false
mold.isDefined mustEqual true
val char = mold.get.asInstanceOf[CharacterData]
char.pos.x mustEqual 3674.8438f
char.pos.y mustEqual 2726.789f
char.pos.z mustEqual 91.15625f
char.objYaw mustEqual 19
char.faction mustEqual 2 //vs
char.bops mustEqual false
char.name mustEqual "IlllIIIlllIlIllIlllIllI"
char.exosuit mustEqual 4 //standard
char.sex mustEqual 2 //female
char.face1 mustEqual 2
char.face2 mustEqual 9
char.voice mustEqual 1 //female 1
char.unk1 mustEqual 0x8080
char.unk2 mustEqual 0xFFFF
char.unk3 mustEqual 2
char.viewPitch mustEqual 0xFF
char.viewYaw mustEqual 0x6A
char.upperMerit mustEqual 0xFFFF //none
char.middleMerit mustEqual 0xFFFF //none
char.lowerMerit mustEqual 0xFFFF //none
char.termOfServiceMerit mustEqual 0xFFFF //none
case default =>
ko
}
}
"decode (9mm)" in { "decode (9mm)" in {
PacketCoding.DecodePacket(string_9mm).require match { PacketCoding.DecodePacket(string_9mm).require match {
case obj @ ObjectCreateMessage(len, cls, guid, parent, mold) => case obj @ ObjectCreateMessage(len, cls, guid, parent, mold) =>
@ -194,7 +231,7 @@ class GamePacketTest extends Specification {
mold.isDefined mustEqual true mold.isDefined mustEqual true
val obj_wep = mold.get.asInstanceOf[WeaponData] val obj_wep = mold.get.asInstanceOf[WeaponData]
obj_wep.unk mustEqual 4 obj_wep.unk mustEqual 4
val obj_ammo = obj_wep.ammo//.asInstanceOf[InternalMold] val obj_ammo = obj_wep.ammo//.asInstanceOf[InternalSlot]
obj_ammo.objectClass mustEqual 28 obj_ammo.objectClass mustEqual 28
obj_ammo.guid mustEqual PlanetSideGUID(1286) obj_ammo.guid mustEqual PlanetSideGUID(1286)
obj_ammo.parentSlot mustEqual 0 obj_ammo.parentSlot mustEqual 0