diff --git a/common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala b/common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala index 90331d0f7..e52a6f45d 100644 --- a/common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala @@ -41,7 +41,7 @@ object ControlPacketOpcode extends Enumeration { RelatedB1, RelatedB2, RelatedB3, - AggregatePacket, // same as MultiPacket, but with the ability to send extended length packets + MultiPacketEx, // same as MultiPacket, but with the ability to send extended length packets Unknown26, Unknown27, Unknown28, @@ -85,7 +85,7 @@ object ControlPacketOpcode extends Enumeration { case RelatedB1 => noDecoder(opcode) case RelatedB2 => noDecoder(opcode) case RelatedB3 => noDecoder(opcode) - case AggregatePacket => noDecoder(opcode) + case MultiPacketEx => control.MultiPacketEx.decode case Unknown26 => noDecoder(opcode) case Unknown27 => noDecoder(opcode) case Unknown28 => noDecoder(opcode) diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 56c79dab0..32957f4cd 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -67,7 +67,7 @@ object GamePacketOpcode extends Enumeration { UnknownMessage45, UnknownMessage46, UnknownMessage47, - UnknownMessage48, + CharacterRequestMessage, LoadMapMessage, // OPCODE 50 @@ -380,7 +380,7 @@ object GamePacketOpcode extends Enumeration { case UnknownMessage45 => noDecoder(opcode) case UnknownMessage46 => noDecoder(opcode) case UnknownMessage47 => noDecoder(opcode) - case UnknownMessage48 => noDecoder(opcode) + case CharacterRequestMessage => game.CharacterRequestMessage.decode case LoadMapMessage => noDecoder(opcode) // OPCODE 50 diff --git a/common/src/main/scala/net/psforever/packet/control/MultiPacketEx.scala b/common/src/main/scala/net/psforever/packet/control/MultiPacketEx.scala new file mode 100644 index 000000000..41b5b149f --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/control/MultiPacketEx.scala @@ -0,0 +1,66 @@ +// Copyright (c) 2016 PSForever.net to present +package net.psforever.packet.control + +import net.psforever.packet.{ControlPacketOpcode, Marshallable, PlanetSideControlPacket} +import scodec.{Attempt, Codec, DecodeResult, SizeBound} +import scodec.bits._ +import scodec.codecs._ + +final case class MultiPacketEx(packets : Vector[ByteVector]) + extends PlanetSideControlPacket { + type Packet = MultiPacketEx + def opcode = ControlPacketOpcode.MultiPacketEx + def encode = MultiPacketEx.encode(this) +} + +object MultiPacketEx extends Marshallable[MultiPacketEx] { + val ffFound: Codec[Boolean] = new Codec[Boolean] { + def sizeBound = SizeBound.exact(0) + def encode(b: Boolean) = + if(b) + Attempt.successful(hex"ff".bits) + else + Attempt.successful(bin"") + def decode(b: BitVector) = { + if (b.length >= 8 && b.take(8) == hex"ff".bits) + Attempt.successful(DecodeResult(true, b.drop(8))) + else + Attempt.successful(DecodeResult(false, b)) + } + + override def toString = "bitsRemaining" + } + + val twoffFound: Codec[Boolean] = new Codec[Boolean] { + def sizeBound = SizeBound.exact(0) + def encode(b: Boolean) = + if(b) + Attempt.successful(hex"ffff".bits) + else + Attempt.successful(bin"") + def decode(b: BitVector) = { + if (b.length >= 16 && b.take(16) == hex"ffff".bits) + Attempt.successful(DecodeResult(true, b.drop(16))) + else + Attempt.successful(DecodeResult(false, b)) + } + + override def toString = "bitsRemaining" + } + + val sizeCodec = either(ffFound, uint8L, + either(twoffFound, uint16L, uint32L).xmap[Long]( + (a : Either[Int, Long]) => a.fold[Long](a => a, a => a), + (a : Long) => + if(a < 0xffff) Left(a.toInt) else Right(a) + ) + ).xmap[Long]( + (a : Either[Int, Long]) => a.fold[Long](a => a, a => a), + (a : Long) => + if(a < 0xff) Left(a.toInt) else Right(a) + ) + + implicit val codec : Codec[MultiPacketEx] = ( + ("packets" | vector(variableSizeBytesLong(sizeCodec, bytes))) + ).as[MultiPacketEx] +} \ No newline at end of file diff --git a/common/src/main/scala/net/psforever/packet/game/CharacterRequestMessage.scala b/common/src/main/scala/net/psforever/packet/game/CharacterRequestMessage.scala new file mode 100644 index 000000000..cf8708795 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/CharacterRequestMessage.scala @@ -0,0 +1,24 @@ +// Copyright (c) 2016 PSForever.net to present +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ + +/** + * Is sent by the PlanetSide client when selecting a character to play from the character selection + * menu. + */ +final case class CharacterRequestMessage(unk : Long, unk2 : Long) + extends PlanetSideGamePacket { + type Packet = CharacterRequestMessage + def opcode = GamePacketOpcode.CharacterRequestMessage + def encode = CharacterRequestMessage.encode(this) +} + +object CharacterRequestMessage extends Marshallable[CharacterRequestMessage] { + implicit val codec : Codec[CharacterRequestMessage] = ( + ("unk1" | uint32L) :: + ("unk2" | uint32L) + ).as[CharacterRequestMessage] +} \ No newline at end of file diff --git a/common/src/test/scala/ControlPacketTest.scala b/common/src/test/scala/ControlPacketTest.scala index 610ee22b5..6189ecd8b 100644 --- a/common/src/test/scala/ControlPacketTest.scala +++ b/common/src/test/scala/ControlPacketTest.scala @@ -129,5 +129,45 @@ class ControlPacketTest extends Specification { PacketCoding.EncodePacket(SlottedMetaPacket(0, 0x10000, hex"00")).require must throwA[IllegalArgumentException] } } + + "MultiPacketEx" should { + val strings = Vector( + hex"00", + hex"01 41", + hex"01 41" ++ hex"02 4142", + hex"fe" ++ ByteVector.fill(0xfe)(0x41), + hex"ffff00" ++ ByteVector.fill(0xff)(0x41), + hex"ff0001" ++ ByteVector.fill(0x100)(0x41), + hex"ff ffff ffff 0000" ++ ByteVector.fill(0x0000ffff)(0x41), + hex"ff ffff 0000 0100" ++ ByteVector.fill(0x00010000)(0x41) + ) + + val packets = Vector( + MultiPacketEx(Vector(ByteVector.empty)), + MultiPacketEx(Vector(hex"41")), + MultiPacketEx(Vector(hex"41", hex"4142")), + MultiPacketEx(Vector(ByteVector.fill(0xfe)(0x41))), + MultiPacketEx(Vector(ByteVector.fill(0xff)(0x41))), + MultiPacketEx(Vector(ByteVector.fill(0x100)(0x41))), + MultiPacketEx(Vector(ByteVector.fill(0x0000ffff)(0x41))), + MultiPacketEx(Vector(ByteVector.fill(0x00010000)(0x41))) + ) + + "decode" in { + for(i <- strings.indices) { + MultiPacketEx.decode(strings{i}.bits).require.value mustEqual packets{i} + } + + true mustEqual true + } + + "encode" in { + for(i <- packets.indices) { + MultiPacketEx.encode(packets{i}).require.toByteVector mustEqual strings{i} + } + + true mustEqual true + } + } } } \ No newline at end of file diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 1b2e8bbf1..a5f079934 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -78,6 +78,15 @@ class WorldSessionActor extends Actor with MDCContextAware { handlePkt(v) } } + case MultiPacketEx(packets) => + packets.foreach { pkt => + PacketCoding.DecodePacket(pkt) match { + case Failure(e) => + log.error(s"Failed to decode inner packet of MultiPacketEx: $e") + case Successful(v) => + handlePkt(v) + } + } case default => log.debug(s"Unhandled ControlPacket $default") }