Document PSPacket.scala

This commit is contained in:
Chord 2016-07-20 00:11:43 -04:00
parent 699bf726ba
commit 7cf4d00376

View file

@ -9,36 +9,45 @@ import scodec.codecs._
import scodec._
import shapeless._
// Base packets
/** The base of all packets */
sealed trait PlanetSidePacket extends Serializable {
def encode : Attempt[BitVector]
def opcode : Enumeration#Value
}
// Used by companion objects to create encoders and decoders
/** Used by companion objects to create encoders and decoders */
trait Marshallable[T] {
implicit val codec : Codec[T]
def encode(a : T) : Attempt[BitVector] = codec.encode(a)
// assert that when decoding a marshallable type, that no bits are left over
def decode(a : BitVector) : Attempt[DecodeResult[T]] = codec.decode(a)
}
/** PlanetSide game packets: net.psforever.packet.game._ */
trait PlanetSideGamePacket extends PlanetSidePacket {
def opcode : GamePacketOpcode.Type
}
/** PlanetSide control packets: net.psforever.packet.control._ */
trait PlanetSideControlPacket extends PlanetSidePacket {
def opcode : ControlPacketOpcode.Type
}
/** PlanetSide crypto packets: net.psforever.packet.crypto._ */
trait PlanetSideCryptoPacket extends PlanetSidePacket {
def opcode : CryptoPacketOpcode.Type
}
// Packet typing
final case class PlanetSidePacketFlags(packetType : PacketType.Value, secured : Boolean)
// Enumeration starts at 1
/** PlanetSide packet type. Used in more complicated packet headers
*
* ResetSequence - Not sure what this is used for or if the name matches what it actually does
* but I saw some code that appeared to reset the packet sequence number when
* this was set
* Unknown2 - Your guess is as good as mine
* Crypto - Used for early crypto packets that are NOT encrypted
* Normal - Used for all non-crypto packets. May or may not be encrypted.
*
* Enumeration starts at 1. That's what I see in IDA */
object PacketType extends Enumeration(1) {
type Type = Value
val ResetSequence, Unknown2, Crypto, Normal = Value
@ -46,9 +55,13 @@ object PacketType extends Enumeration(1) {
implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint4L)
}
/** PlanetSide packet flags (beginning of most packets) */
final case class PlanetSidePacketFlags(packetType : PacketType.Value, secured : Boolean)
/** Codec for [[PlanetSidePacketFlags]] */
object PlanetSidePacketFlags extends Marshallable[PlanetSidePacketFlags] {
implicit val codec : Codec[PlanetSidePacketFlags] = (
("packet_type" | PacketType.codec) ::
("packet_type" | PacketType.codec) :: // first 4-bits
("unused" | constant(bin"0")) ::
("secured" | bool) ::
("advanced" | constant(bin"1")) :: // we only support "advanced packets"
@ -58,30 +71,23 @@ object PlanetSidePacketFlags extends Marshallable[PlanetSidePacketFlags] {
//////////////////////////////////////////////////
/*class MarshallableEnum[+T] extends Enumeration {
type StorageType = Codec[Int]
implicit val storageType : StorageType = uint8
assert(maxId <= Math.pow(storageType.sizeBound.exact.get, 2),
this.getClass.getCanonicalName + ": maxId exceeds primitive type")
implicit val codec: Codec[T] = PacketHelpers.createEnumerationCodec(this, storageType)
}*/
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
Codec[HNil].xmap[T](from, to)
}
// NOTE: enumerations in scala can't be represented by more than an Int anyways, so this conversion shouldnt matter.
// This is only to overload createEnumerationCodec to work with uint32[L] codecs (which are Long)
def createLongEnumerationCodec[E <: Enumeration](enum : E, storageCodec : Codec[Long]) : Codec[E#Value] = {
createEnumerationCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong))
}
/** Create a Codec for an enumeration type that can correctly represent its value
*
* @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
@ -110,9 +116,21 @@ object PacketHelpers {
struct.narrow[E#Value](from, to)
}
// when the first bit of the byte is set, the size can be between [0, 127].
// otherwise, it is between [128, 32767] and two bytes are used for encoding
// The magic in this is next level
/** Same as [[createEnumerationCodec]] but with a Codec type of Long
*
* NOTE: enumerations in scala can't be represented by more than an Int anyways, so this conversion shouldnt matter.
* This is only to overload createEnumerationCodec to work with uint32[L] codecs (which are Long)
*/
def createLongEnumerationCodec[E <: Enumeration](enum : E, storageCodec : Codec[Long]) : Codec[E#Value] = {
createEnumerationCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong))
}
/** Common codec for how PlanetSide stores string sizes
*
* When the first bit of the byte is set, the size can be between [0, 127].
* Otherwise, it is between [128, 32767] and two bytes are used for encoding.
* The magic in this is next level (read as: SCodec makes things hard to understand)
*/
def encodedStringSize : Codec[Int] = either(bool, uint(15), uint(7)).
xmap[Int](
(a : Either[Int, Int]) => a.fold[Int](a => a, a => a),
@ -121,6 +139,44 @@ object PacketHelpers {
if(a > 0x7f) Left(a) else Right(a)
)
private def encodedStringSizeWithPad(pad : Int) : Codec[Int] = encodedStringSize <~ ignore(pad)
/** 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
*/
def encodedStringAligned(adjustment : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithPad(adjustment), ascii)
/** Variable for the charset that PlanetSide uses for unicode (2 byte unicode) */
val utf16 = string(Charset.forName("UTF-16LE"))
/** Common codec for PlanetSide wchar_t strings (wide strings, UTF-16)
*
* An encoded *wide* string is twice the length of the given encoded size and half of the length of the
* 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)
), 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
), utf16)
// TODO: make the function below work as there are places it should be used
/*private def encodedStringSizeWithLimit(limit : Int) : Codec[Int] = {
either(bool, uint(15), uint(7)).
exmap[Int](
@ -143,24 +199,8 @@ object PacketHelpers {
Right(a)
}
)
}*/
}
private def encodedStringSizeWithPad(pad : Int) : Codec[Int] = encodedStringSize <~ ignore(pad)
def encodedString : Codec[String] = variableSizeBytes(encodedStringSize, ascii)
//def encodedStringWithLimit(limit : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithLimit(limit), ascii)
def encodedStringAligned(adjustment : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithPad(adjustment), ascii)
/// Variable for the charset that planetside uses for unicode
val utf16 = string(Charset.forName("UTF-16LE"))
/// An encoded *wide* string is twice the length of the given encoded size and half of the length of the
/// 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,
outSize => outSize/2), utf16)
def encodedWideStringAligned(adjustment : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithPad(adjustment).xmap(
insize => insize*2,
outSize => outSize/2), utf16)
def encodedStringWithLimit(limit : Int) : Codec[String] = variableSizeBytes(encodedStringSizeWithLimit(limit), ascii)
*/
}