diff --git a/common/src/main/scala/net/psforever/packet/PSPacket.scala b/common/src/main/scala/net/psforever/packet/PSPacket.scala
index 2fe9a5630..3dab2fbf2 100644
--- a/common/src/main/scala/net/psforever/packet/PSPacket.scala
+++ b/common/src/main/scala/net/psforever/packet/PSPacket.scala
@@ -74,38 +74,35 @@ object PlanetSidePacketFlags extends Marshallable[PlanetSidePacketFlags] {
object PacketHelpers {
/** Used in certain instances where Codec defintions are stubbed out */
def emptyCodec[T](instance : T) = {
- def to(pkt: T) = HNil
- def from(a: HNil) = instance
+ def to(pkt : T) = HNil
+ def from(a : HNil) = instance
Codec[HNil].xmap[T](from, to)
}
-
-
/** Create a Codec for an enumeration type that can correctly represent its value
- *
- * @param enum the enumeration type to create a codec for
+ * @param enum the enumeration type to create a codec for
* @param storageCodec the Codec used for actually representing the value
* @tparam E The inferred type
* @return Generated codec
*/
def createEnumerationCodec[E <: Enumeration](enum : E, storageCodec : Codec[Int]) : Codec[E#Value] = {
type Struct = Int :: HNil
- val struct: Codec[Struct] = storageCodec.hlist
+ val struct : Codec[Struct] = storageCodec.hlist
val primitiveLimit = Math.pow(2, storageCodec.sizeBound.exact.get)
// Assure that the enum will always be able to fit in a N-bit int
assert(enum.maxId <= primitiveLimit,
enum.getClass.getCanonicalName + s": maxId exceeds primitive type (limit of $primitiveLimit, maxId ${enum.maxId})")
- def to(pkt: E#Value): Struct = {
+ def to(pkt : E#Value) : Struct = {
pkt.id :: HNil
}
- def from(struct: Struct): Attempt[E#Value] = struct match {
+ def from(struct : Struct) : Attempt[E#Value] = struct match {
case enumVal :: HNil =>
// verify that this int can match the enum
val first = enum.values.firstKey.id
- val last = enum.maxId-1
+ val last = enum.maxId - 1
if(enumVal >= first && enumVal <= last)
Attempt.successful(enum(enumVal))
@@ -144,13 +141,11 @@ object PacketHelpers {
/** Codec for how PlanetSide represents strings on the wire */
def encodedString : Codec[String] = variableSizeBytes(encodedStringSize, ascii)
-
/** Same as [[encodedString]] but with a bit adjustment
*
* This comes in handy when a PlanetSide string is decoded on a non-byte boundary. The PlanetSide client
* will byte align after decoding the string lenght, but BEFORE the string itself. Scodec doesn't like this
* variability and there doesn't appear to be a way to fix this issue.
- *
* @param adjustment The adjustment amount in bits
* @return Generated string decoding codec with adjustment
*/
@@ -165,15 +160,15 @@ object PacketHelpers {
* input string. We use xmap to transform the [[encodedString]] codec as this change is just a division and multiply
*/
def encodedWideString : Codec[String] = variableSizeBytes(encodedStringSize.xmap(
- insize => insize*2, // number of symbols -> number of bytes (decode)
- outSize => outSize/2 // number of bytes -> number of symbols (encode)
+ insize => insize * 2, // number of symbols -> number of bytes (decode)
+ outSize => outSize / 2 // number of bytes -> number of symbols (encode)
), utf16)
/** Same as [[encodedWideString]] but with a bit alignment after the decoded size
*/
def encodedWideStringAligned(adjustment : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithPad(adjustment).xmap(
- insize => insize*2,
- outSize => outSize/2
+ insize => insize * 2,
+ outSize => outSize / 2
), utf16)
// TODO: make the function below work as there are places it should be used
@@ -203,4 +198,105 @@ object PacketHelpers {
def encodedStringWithLimit(limit : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithLimit(limit), ascii)
*/
+
+ /**
+ * Encode and decode a byte-aligned `List`.
+ *
+ * This function is copied almost verbatim from its source, with exception of swapping the normal `ListCodec` for a new `AlignedListCodec`.
+ * @param countCodec the codec that represents the prefixed size of the `List`
+ * @param alignment the number of bits padded between the `List` size and the `List` contents
+ * @param valueCodec a codec that describes each of the contents of the `List`
+ * @tparam A the type of the `List` contents
+ * @see codec\package.scala, listOfN
+ * @return a codec that works on a List of A
+ */
+ def listOfNAligned[A](countCodec : Codec[Long], alignment : Int, valueCodec : Codec[A]) : Codec[List[A]] = {
+ countCodec.
+ flatZip { count => new AlignedListCodec(countCodec, valueCodec, alignment, Some(count)) }.
+ narrow[List[A]]({ case (cnt, xs) =>
+ if(xs.size == cnt) Attempt.successful(xs)
+ else Attempt.failure(Err(s"Insufficient number of elements: decoded ${xs.size} but should have decoded $cnt"))
+ }, xs => (xs.size, xs)).
+ withToString(s"listOfN($countCodec, $valueCodec)")
+ }
+
+ /**
+ * Codec that encodes/decodes a list of `n` elements, where `n` is known at compile time.
+ *
+ * This function is copied almost verbatim from its source, with exception of swapping the parameter that is normally a `Nat` `literal`.
+ * The modified function takes a normal unsigned `Integer` and assures that the parameter is non-negative before further processing.
+ * @param size the known size of the `List`
+ * @param codec a codec that describes each of the contents of the `List`
+ * @tparam A the type of the `List` contents
+ * @see codec\package.scala, sizedList
+ * @see codec\package.scala, listOfN
+ * @see codec\package.scala, provides
+ * @return a codec that works on a List of A but excludes the size from the encoding
+ */
+ def listOfNSized[A](size : Long, codec : Codec[A]) : Codec[List[A]] = PacketHelpers.listOfNAligned(provide(if(size < 0) 0 else size), 0, codec)
+}
+
+/**
+ * The codec that encodes and decodes a byte-aligned `List`.
+ *
+ * This class is copied almost verbatim from its source, with only heavy modifications to its `encode` process.
+ * @param countCodec the codec that represents the prefixed size of the `List`
+ * @param valueCodec a codec that describes each of the contents of the `List`
+ * @param alignment the number of bits padded between the `List` size and the `List` contents (on successful)
+ * @param limit the number of elements in the `List`
+ * @tparam A the type of the `List` contents
+ * @see ListCodec.scala
+ */
+private class AlignedListCodec[A](countCodec : Codec[Long], valueCodec: Codec[A], alignment : Int, limit: Option[Long] = None) extends Codec[List[A]] {
+ /**
+ * Convert a `List` of elements into a byte-aligned `BitVector`.
+ *
+ * Bit padding after the encoded size of the `List` is only added if the `alignment` value is greater than zero and the initial encoding process was successful.
+ * The padding is rather heavy-handed and a completely different `BitVector` is returned if successful.
+ * Performance hits for this complexity are not expected to be significant.
+ * @param list the `List` to be encoded
+ * @return the `BitVector` encoding, if successful
+ */
+ override def encode(list : List[A]) : Attempt[BitVector] = {
+ val solve : Attempt[BitVector] = Encoder.encodeSeq(valueCodec)(list)
+ if(alignment > 0) {
+ solve match {
+ case Attempt.Successful(vector) =>
+ val countCodecSize : Long = countCodec.sizeBound.lowerBound
+ return Attempt.successful(vector.take(countCodecSize) ++ BitVector.fill(alignment)(false) ++ vector.drop(countCodecSize))
+ case _ =>
+ return Attempt.failure(Err("failed to create a list"))
+ }
+ }
+ solve
+ }
+
+ /**
+ * Convert a byte-aligned `BitVector` into a `List` of elements.
+ * @param buffer the encoded bits in the `List`, preceded by the alignment bits
+ * @return the decoded `List`
+ */
+ def decode(buffer: BitVector) = {
+ val lim = Option( if(limit.isDefined) limit.get.asInstanceOf[Int] else 0 ) //TODO potentially unsafe size conversion
+ Decoder.decodeCollect[List, A](valueCodec, lim)(buffer.drop(alignment))
+ }
+
+ /**
+ * The size of the encoded `List`.
+ *
+ * Unchanged from original.
+ * @return the size as calculated by the size of each element for each element
+ */
+ def sizeBound = limit match {
+ case None => SizeBound.unknown
+ case Some(lim) => valueCodec.sizeBound * lim
+ }
+
+ /**
+ * Get a `String` representation of this `List`.
+ *
+ * Unchanged from original.
+ * @return the `String` representation
+ */
+ override def toString = s"list($valueCodec)"
}
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 b397ff552..61d7d5523 100644
--- a/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/ObjectCreateMessage.scala
@@ -1,13 +1,13 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+//import net.psforever.types.Vector3
import scodec.bits._
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless._
import scala.annotation.switch
-import scala.util.Try
abstract class ConstructorData
@@ -31,6 +31,190 @@ object AmmoBoxData extends Marshallable[AmmoBoxData] {
).as[AmmoBoxData]
}
+case class WeaponData(ammo : InternalMold) extends ConstructorData
+
+object WeaponData extends Marshallable[WeaponData] {
+ implicit val codec : Codec[WeaponData] = (
+ ("code" | uint16L) ::
+ ignore(12) ::
+ uint4L ::
+ ignore(4) ::
+ uintL(12) ::
+ ("data" | InternalMold.codec)
+ ).exmap[WeaponData] (
+ {
+ case 0x88 :: _ :: 2 :: _ :: 0x2C0 :: ammo :: HNil =>
+ Attempt.successful(WeaponData(ammo))
+ case x :: _ :: y :: _ :: z :: _ :: HNil =>
+ Attempt.failure(Err("code wrong - looking for 136-2-704, found %d-%d-%d".format(x,y,z)))
+ },
+ {
+ case WeaponData(ammo) =>
+ Attempt.successful(0x88 :: () :: 2 :: () :: 0x2C0 :: ammo :: HNil)
+ }
+ ).as[WeaponData]
+}
+
+//case class CharacterData(pos : Vector3,
+// obj_yaw : Int,
+// faction : Int,
+// bops : Boolean,
+// name : String,
+// exosuit : Int,
+// sex : Int,
+// face1 : Int,
+// face2 : Int,
+// voice : Int,
+// unk1 : Int, //0x8080
+// unk2 : Int, //0xFFFF or 0x0
+// unk3 : Int, //2
+// anchor : Boolean,
+// viewPitch : Int,
+// viewYaw : Int,
+// upperMerit : Int, //0xFFFF means no merit (for all ...)
+// middleMerit : Int,
+// lowerMerit : Int,
+// termOfServiceMerit : Int,
+// healthMax : Int,
+// health : Int,
+// armor : Int,
+// unk4 : Int, //1
+// unk5 : Int, //7
+// unk6 : Int, //7
+// staminaMax : Int,
+// stamina : Int,
+// unk7 : Int, // 192
+// unk8 : Int, //66
+// unk9 : Int, //197
+// unk10 : Int, //70
+// unk11 : Int, //134
+// unk12 : Int, //199
+// firstTimeEvent_length : Long,
+// firstEntry : String,
+// firstTimEvent_list : List[String],
+// tutorial_list : List[String],
+// inventory : BitVector
+// ) extends ConstructorData
+//
+//object CharacterData extends Marshallable[CharacterData] {
+// //all ignore()s are intentional; please do not mess with them
+// implicit val codec : Codec[CharacterData] = (
+// ("pos" | Vector3.codec_pos) ::
+// ignore(16) ::
+// ("obj_yaw" | uint8L) ::
+// ignore(1) ::
+// ("faction" | uintL(2)) ::
+// ("bops" | bool) ::
+// ignore(4) ::
+// ignore(16) ::
+// ("name" | PacketHelpers.encodedWideStringAligned(4)) ::
+// ("exosuit" | uintL(3)) ::
+// ignore(1) ::
+// ignore(1) ::
+// ("sex" | uintL(2)) ::
+// ("face1" | uint4L) ::
+// ("face2" | uint4L) ::
+// ("voice" | uintL(3)) ::
+// ignore(2) ::
+// ignore(4) ::
+// ignore(16) ::
+// ("unk1" | uint16L) ::
+// ignore(40) ::
+// ignore(2) ::
+// ("unk2" | uint16L) ::
+// ignore(2) ::
+// ignore(28) ::
+// ("unk3" | uintL(4)) ::
+// ignore(4) ::
+// ignore(16) ::
+// ignore(2) ::
+// ("anchor" | bool) ::
+// ignore(1) ::
+// ("viewPitch" | uint8L) ::
+// ("viewYaw" | uint8L) ::
+// ignore(4) ::
+// ignore(4) ::
+// ignore(2) ::
+// ("upperMerit" | uint16L) ::
+// ("middleMerit" | uint16L) ::
+// ("lowerMerit" | uint16L) ::
+// ("termOfServiceMerit" | uint16L) ::
+// ignore(2) ::
+// ignore(156) ::
+// ignore(2) ::
+// ("healthMax" | uint16L) ::
+// ("health" | uint16L) ::
+// ignore(1) ::
+// ("armor" | uint16L) ::
+// ignore(1) ::
+// ignore(8) ::
+// ("unk4" | uint8L) ::
+// ignore(8) ::
+// ("unk5" | uint4L) ::
+// ("unk6" | uintL(3)) ::
+// ("staminaMax" | uint16L) ::
+// ("stamina" | uint16L) ::
+// ignore(148) ::
+// ignore(4) ::
+// ("unk7" | uint16L) ::
+// ("unk8" | uint8L) ::
+// ("unk9" | uint8L) ::
+// ("unk10" | uint8L) ::
+// ("unk11" | uint8L) ::
+// ("unk12" | uintL(12)) ::
+// ignore(3) ::
+// (("firstTimeEvent_length" | uint32L) >>:~ { len =>
+// ("firstEntry" | PacketHelpers.encodedStringAligned(5)) ::
+// ("firstTimeEvent_list" | PacketHelpers.listOfNSized(len - 1, PacketHelpers.encodedString))
+// }) ::
+// ("tutorial_list" | PacketHelpers.listOfNAligned(uint32L, 0, PacketHelpers.encodedString)) ::
+// ignore(204) ::
+// ignore(3) ::
+// ("inventory" | bits)
+// ).as[CharacterData]
+//}
+
+case class InternalMold(objectClass : Int,
+ guid : PlanetSideGUID,
+ parentSlot : Int,
+ obj : Option[ConstructorData])
+
+object InternalMold extends Marshallable[InternalMold] {
+ type objPattern = Int :: PlanetSideGUID :: Int :: Option[ConstructorData] :: HNil
+
+ implicit val codec : Codec[InternalMold] = (
+ ignore(1) :: //TODO determine what this bit does
+ ("objectClass" | uintL(11)) ::
+ ("guid" | PlanetSideGUID.codec) ::
+ ("parentSlot" | PacketHelpers.encodedStringSize) ::
+ ("data" | bits)
+ ).exmap[objPattern] (
+ {
+ case _ :: cls :: guid :: slot :: data :: HNil =>
+ Attempt.successful(cls :: guid :: slot :: Mold.selectMold(cls, data) :: HNil)
+ },
+ {
+ case cls :: guid :: slot :: None :: HNil =>
+ Attempt.failure(Err("no constuctor data could be found"))
+ case cls :: guid :: slot :: mold :: HNil =>
+ Attempt.successful(() :: cls :: guid :: slot :: Mold.serialize(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[InternalMold]
+}
+
case class Mold(objectClass : Int,
data : BitVector) {
private var obj : Option[ConstructorData] = Mold.selectMold(objectClass, data)
@@ -53,37 +237,45 @@ object Mold {
def apply(objectClass : Int, obj : ConstructorData) : Mold =
new Mold( objectClass, Mold.serialize(objectClass, obj) )
- private def selectMold(objClass : Int, data : BitVector) : Option[ConstructorData] = {
+ def selectMold(objClass : Int, data : BitVector) : Option[ConstructorData] = {
var out : Option[ConstructorData] = None
if(!data.isEmpty) {
(objClass : @switch) match {
- case 0x1C =>
+ case 0x1C => //9mm
val opt = AmmoBoxData.codec.decode(data).toOption
- if(opt.isDefined) {
+ if(opt.isDefined)
+ out = Some(opt.get.value)
+ case 0x46 => //beamer
+ val opt = WeaponData.codec.decode(data).toOption
+ if(opt.isDefined)
out = Some(opt.get.value)
- }
case _ =>
- out = None
}
}
out
}
- private def serialize(objClass : Int, obj : ConstructorData) : BitVector = {
+ def serialize(objClass : Int, obj : ConstructorData) : BitVector = {
var out = BitVector.empty
try {
(objClass : @switch) match {
- case 0x1C =>
+ case 0x1C => //9mm
val opt = AmmoBoxData.codec.encode(obj.asInstanceOf[AmmoBoxData]).toOption
- if(opt.isDefined) {
+ if(opt.isDefined)
out = opt.get
- }
+ case 0x46 => //beamer
+ val opt = WeaponData.codec.encode(obj.asInstanceOf[WeaponData]).toOption
+ if(opt.isDefined)
+ out = opt.get
+ case _ =>
+ throw new ClassCastException("cannot find object code - "+objClass)
}
}
catch {
- case ex : ClassCastException => {
+ case ex : ClassCastException =>
//TODO generate and log wrong class error message
- }
+ case ex : Exception =>
+ //TODO generic error
}
out
}