mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-20 02:54:46 +00:00
prototype for weapon data codec
This commit is contained in:
parent
1f629cf117
commit
72b400e1d0
|
|
@ -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`.<br>
|
||||
* <br>
|
||||
* 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.<br>
|
||||
* <br>
|
||||
* 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`.<br>
|
||||
* <br>
|
||||
* 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`.<br>
|
||||
* <br>
|
||||
* 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`.<br>
|
||||
* <br>
|
||||
* 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`.<br>
|
||||
* <br>
|
||||
* Unchanged from original.
|
||||
* @return the `String` representation
|
||||
*/
|
||||
override def toString = s"list($valueCodec)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue