diff --git a/src/main/scala/net/psforever/actors/net/MiddlewareActor.scala b/src/main/scala/net/psforever/actors/net/MiddlewareActor.scala index 36db2b5a..a5093e1d 100644 --- a/src/main/scala/net/psforever/actors/net/MiddlewareActor.scala +++ b/src/main/scala/net/psforever/actors/net/MiddlewareActor.scala @@ -1,7 +1,7 @@ package net.psforever.actors.net import akka.actor.Cancellable -import akka.actor.typed.{ActorRef, ActorTags, Behavior, PostStop, Signal} +import akka.actor.typed._ import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.io.Udp import java.net.InetSocketAddress @@ -19,8 +19,8 @@ import scodec.Attempt.{Failure, Successful} import scodec.bits.{BitVector, ByteVector, HexStringSyntax} import scodec.interop.akka.EnrichedByteVector import net.psforever.objects.Default -import net.psforever.packet.{CryptoPacketOpcode, PacketCoding, PlanetSideControlPacket, PlanetSideCryptoPacket, PlanetSideGamePacket, PlanetSidePacket} -import net.psforever.packet.control.{ClientStart, ConnectionClose, ControlSync, ControlSyncResp, HandleGamePacket, MultiPacket, MultiPacketEx, RelatedA, RelatedB, ServerStart, SlottedMetaPacket, TeardownConnection} +import net.psforever.packet._ +import net.psforever.packet.control._ import net.psforever.packet.crypto.{ClientChallengeXchg, ClientFinished, ServerChallengeXchg, ServerFinished} import net.psforever.packet.game.{ChangeFireModeMessage, CharacterInfoMessage, KeepAliveMessage, PingMsg} import net.psforever.packet.PacketCoding.CryptoCoding @@ -522,8 +522,9 @@ class MiddlewareActor( log.error(s"Unexpected crypto packet '$packet'") Behaviors.same - case packet => - log.error(s"Unexpected type of packet '$packet'") + case _: PlanetSideResetSequencePacket => + log.debug("Received sequence reset request from client; complying") + outSequence = 0 Behaviors.same } } diff --git a/src/main/scala/net/psforever/packet/PSPacket.scala b/src/main/scala/net/psforever/packet/PSPacket.scala index acf2e4ae..e5262d55 100644 --- a/src/main/scala/net/psforever/packet/PSPacket.scala +++ b/src/main/scala/net/psforever/packet/PSPacket.scala @@ -40,7 +40,9 @@ trait PlanetSideCryptoPacket extends PlanetSidePacket { } /** PlanetSide ResetSequence packets: self-contained? */ -trait PlanetSideResetSequencePacket extends PlanetSidePacket { } +trait PlanetSideResetSequencePacket extends PlanetSidePacket { + def opcode: ResetSequenceOpcode.Type +} /** PlanetSide packet type. Used in more complicated packet headers * diff --git a/src/main/scala/net/psforever/packet/PacketCoding.scala b/src/main/scala/net/psforever/packet/PacketCoding.scala index a67e26ee..4c494610 100644 --- a/src/main/scala/net/psforever/packet/PacketCoding.scala +++ b/src/main/scala/net/psforever/packet/PacketCoding.scala @@ -5,6 +5,7 @@ import java.security.{Key, Security} import javax.crypto.Cipher import javax.crypto.spec.RC5ParameterSpec +import net.psforever.packet.reset.ResetSequence import scodec.Attempt.{Failure, Successful} import scodec.bits._ import scodec.{Attempt, Codec, DecodeResult, Err} @@ -118,6 +119,13 @@ object PacketCoding { case Successful(opcode) => Successful(opcode ++ payload) case f @ Failure(_) => f } + + case packet: PlanetSideResetSequencePacket => + ResetSequenceOpcode.codec.encode(packet.opcode) match { + case Successful(opcode) => Successful(opcode) + case f @ Failure(_) => f + } + case _ => Failure(Err("packet not supported")) } @@ -208,8 +216,11 @@ object PacketCoding { case (PacketType.Normal, None) => Failure(Err("Cannot unmarshal encrypted packet without a cipher")) case (PacketType.ResetSequence, Some(_crypto)) => - val test = _crypto.decrypt(payload.drop(1)) - Failure(Err(s"ResetSequence not completely supported, but: $flags, $sequence, and $payload; decrypt: $test")) + _crypto.decrypt(payload.drop(1)) match { + case Successful(p) if p == hex"01" => Successful((ResetSequence(), sequence)) + case Successful(p) => Failure(Err(s"ResetSequence decrypted to unsupported value - $p")) + case _ => Failure(Err(s"ResetSequence did not decrypt properly")) + } case (ptype, _) => Failure(Err(s"Cannot unmarshal $ptype packet at all")) } @@ -243,9 +254,12 @@ object PacketCoding { GamePacketOpcode.codec.decode(msg.bits) match { case Successful(opcode) => GamePacketOpcode.getPacketDecoder(opcode.value)(opcode.remainder) match { + case Failure(_) if opcode.value.id == 1 => + Successful(ResetSequence()) case Failure(e) => Failure(Err(f"Failed to parse game packet 0x${opcode.value.id}%02x: " + e.messageWithContext)) - case Successful(p) => Successful(p.value) + case Successful(p) => + Successful(p.value) } case Failure(e) => Failure(Err("Failed to decode game packet's opcode: " + e.message)) } diff --git a/src/main/scala/net/psforever/packet/ResetSequenceOpcode.scala b/src/main/scala/net/psforever/packet/ResetSequenceOpcode.scala new file mode 100644 index 00000000..116f19e5 --- /dev/null +++ b/src/main/scala/net/psforever/packet/ResetSequenceOpcode.scala @@ -0,0 +1,24 @@ +// Copyright (c) 2020 PSForever +package net.psforever.packet + +import scodec.bits.BitVector +import scodec.{Attempt, Codec, DecodeResult, Err} +import scodec.codecs.uint8L + +object ResetSequenceOpcode extends Enumeration(1) { + type Type = Value + val ResetSequence = Value + + def getPacketDecoder(opcode: ResetSequenceOpcode.Type): BitVector => Attempt[DecodeResult[PlanetSideResetSequencePacket]] = + opcode match { + case ResetSequence => reset.ResetSequence.decode + case _ => + (_: BitVector) => + Attempt.failure( + Err(s"Could not find a marshaller for reset sequence packet $opcode") + .pushContext("get_marshaller") + ) + } + + implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint8L) +} diff --git a/src/main/scala/net/psforever/packet/reset/ResetSequence.scala b/src/main/scala/net/psforever/packet/reset/ResetSequence.scala new file mode 100644 index 00000000..95bec7a4 --- /dev/null +++ b/src/main/scala/net/psforever/packet/reset/ResetSequence.scala @@ -0,0 +1,17 @@ +// Copyright (c) 2020 PSForever +package net.psforever.packet.reset + +import net.psforever.packet.{Marshallable, PacketHelpers, PlanetSideResetSequencePacket, ResetSequenceOpcode} +import scodec.{Attempt, Codec} +import scodec.bits.BitVector + +final case class ResetSequence() + extends PlanetSideResetSequencePacket { + type Packet = ResetSequence + def opcode: ResetSequenceOpcode.Type = ResetSequenceOpcode.ResetSequence + def encode: Attempt[BitVector] = ResetSequence.encode(this) +} + +object ResetSequence extends Marshallable[ResetSequence] { + implicit val codec: Codec[ResetSequence] = PacketHelpers.emptyCodec[ResetSequence](ResetSequence()) +} diff --git a/src/test/scala/reset/ResetSequenceTest.scala b/src/test/scala/reset/ResetSequenceTest.scala new file mode 100644 index 00000000..dc60535e --- /dev/null +++ b/src/test/scala/reset/ResetSequenceTest.scala @@ -0,0 +1,25 @@ +// Copyright (c) 2021 PSForever +package reset + +import net.psforever.packet._ +import net.psforever.packet.reset.ResetSequence +import org.specs2.mutable._ +import scodec.bits._ + +class ResetSequenceTest extends Specification { + val string = hex"01" + + "decode" in { + PacketCoding.decodePacket(string).require match { + case ResetSequence() => ok + case _ => ko + } + } + + "encode" in { + val msg = ResetSequence() + val pkt = PacketCoding.encodePacket(msg).require.toByteVector + + pkt mustEqual string + } +}