mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-02-21 23:53:36 +00:00
The game uses a UDP-based protocol. Unlike TCP, UDP does not guarantee that packets arrive, or that they arrive in the correct order. For this reason, the game protocol implements those features using the following: * All packets have a sequence number that is utilized for reordering * Important packets are wrapped in a SlottedMetaPacket with a subslot number * RelatedA packets ae used to request lost packets using the subslot number * RelatedB packets are used to confirm received SlottedMetaPackets All of these go both ways, server <-> client. We used to only partially implement these features: Outgoing packet bundles used SMPs and could be resent, but not all packets were bundled and there was no logic for requesting lost packets from the client and there was no packet reordering, which resulted in dire consequences in the case of packet loss (zoning failures, crashes and many other odd bugs). This patch addresses all of these issues. * Packet bundling: Packets are now automatically bundled and sent as SlottedMetaPackets using a recurring timer. All manual bundling functionality was removed. * Packet reordering: Incoming packets, if received out of order, are stashed and reordered. The maximum wait time for reordering is 20ms. * Packet requesting: Missing SlottedMetaPackets are requested from the client. * PacketCoding refactor: Dropped confusing packet container types. Fixes #5. * Crypto rewrite: PSCrypto is based on a ancient buggy version of cryptopp. Updating to a current version was not possible because it removed the MD5-MAC algorithm. For more details, see Md5Mac.scala. This patch replaces PSCrypto with native Scala code. * Added two new actors: * SocketActor: A simple typed UDP socket actor * MiddlewareActor: The old session pipeline greatly simplified into a typed actor that does most of the things mentioned above. * Begun work on a headless client * Fixed anniversary gun breaking stamina regen * Resolved a few sentry errors
336 lines
15 KiB
Scala
336 lines
15 KiB
Scala
// Copyright (c) 2017 PSForever
|
|
package game
|
|
|
|
import org.specs2.mutable._
|
|
import net.psforever.packet._
|
|
import net.psforever.packet.game._
|
|
import scodec.bits._
|
|
|
|
class BattleplanMessageTest extends Specification {
|
|
val string_start =
|
|
hex"b3 3a197902 94 59006500740041006e006f0074006800650072004600610069006c0075007200650041006c007400 0000 01 e0"
|
|
val string_stop =
|
|
hex"b3 3a197902 94 59006500740041006e006f0074006800650072004600610069006c0075007200650041006c007400 0000 01 f0"
|
|
val string_line =
|
|
hex"b3 85647702 8c 4f0075007400730074006100620075006c006f0075007300 0a00 20 2aba2b4aae8bd2aba334aae8dd2aca3b4ab28fd2aca414ab29152aca474ab292d2ada4d4ab69452ada534ab695d2ada594ab696d2ada5d4ab697d2ada614ab698d2ada654ab699d2ada694ab69ad2aea6d4aba9bd2aea714aba9cd2aea754aba9dd2aea794aba9ed"
|
|
val string_style = hex"b3856477028c4f0075007400730074006100620075006c006f00750073000a00031d22aba2f4aae8cd"
|
|
val string_message =
|
|
hex"b3 85647702 8c 4f0075007400730074006100620075006c006f0075007300 0a00 01 6aba2b5011c0480065006c006c006f00200041007500720061007800690073002100"
|
|
//0xb3856477028c4f0075007400730074006100620075006c006f00750073000a000130
|
|
|
|
"decode (start)" in {
|
|
PacketCoding.decodePacket(string_start).require match {
|
|
case BattleplanMessage(char_id, player_name, zone_id, diagrams) =>
|
|
char_id mustEqual 41490746
|
|
player_name mustEqual "YetAnotherFailureAlt"
|
|
zone_id mustEqual 0
|
|
diagrams.size mustEqual 1
|
|
//0
|
|
diagrams.head.action mustEqual DiagramActionCode.StartDrawing
|
|
diagrams.head.stroke.isDefined mustEqual false
|
|
case _ =>
|
|
ko
|
|
}
|
|
}
|
|
|
|
"decode (end)" in {
|
|
PacketCoding.decodePacket(string_stop).require match {
|
|
case BattleplanMessage(char_id, player_name, zone_id, diagrams) =>
|
|
char_id mustEqual 41490746
|
|
player_name mustEqual "YetAnotherFailureAlt"
|
|
zone_id mustEqual 0
|
|
diagrams.size mustEqual 1
|
|
//0
|
|
diagrams.head.action mustEqual DiagramActionCode.StopDrawing
|
|
diagrams.head.stroke.isDefined mustEqual false
|
|
case _ =>
|
|
ko
|
|
}
|
|
}
|
|
|
|
"decode (stop)" in {
|
|
PacketCoding.decodePacket(string_line).require match {
|
|
case BattleplanMessage(char_id, player_name, zone_id, diagrams) =>
|
|
char_id mustEqual 41378949
|
|
player_name mustEqual "Outstabulous"
|
|
zone_id mustEqual 10
|
|
diagrams.size mustEqual 32
|
|
//0
|
|
diagrams.head.action mustEqual DiagramActionCode.Vertex
|
|
diagrams.head.stroke.isDefined mustEqual true
|
|
diagrams.head.stroke.get.isInstanceOf[Vertex] mustEqual true
|
|
diagrams.head.stroke.get.asInstanceOf[Vertex].x mustEqual 7512.0f
|
|
diagrams.head.stroke.get.asInstanceOf[Vertex].y mustEqual 6312.0f
|
|
//1
|
|
diagrams(1).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(1).stroke.get.asInstanceOf[Vertex].x mustEqual 7512.0f
|
|
diagrams(1).stroke.get.asInstanceOf[Vertex].y mustEqual 6328.0f
|
|
//2
|
|
diagrams(2).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(2).stroke.get.asInstanceOf[Vertex].x mustEqual 7512.0f
|
|
diagrams(2).stroke.get.asInstanceOf[Vertex].y mustEqual 6344.0f
|
|
//3
|
|
diagrams(3).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(3).stroke.get.asInstanceOf[Vertex].x mustEqual 7512.0f
|
|
diagrams(3).stroke.get.asInstanceOf[Vertex].y mustEqual 6360.0f
|
|
//4
|
|
diagrams(4).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(4).stroke.get.asInstanceOf[Vertex].x mustEqual 7520.0f
|
|
diagrams(4).stroke.get.asInstanceOf[Vertex].y mustEqual 6376.0f
|
|
//5
|
|
diagrams(5).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(5).stroke.get.asInstanceOf[Vertex].x mustEqual 7520.0f
|
|
diagrams(5).stroke.get.asInstanceOf[Vertex].y mustEqual 6392.0f
|
|
//6
|
|
diagrams(6).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(6).stroke.get.asInstanceOf[Vertex].x mustEqual 7520.0f
|
|
diagrams(6).stroke.get.asInstanceOf[Vertex].y mustEqual 6400.0f
|
|
//7
|
|
diagrams(7).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(7).stroke.get.asInstanceOf[Vertex].x mustEqual 7520.0f
|
|
diagrams(7).stroke.get.asInstanceOf[Vertex].y mustEqual 6416.0f
|
|
//8
|
|
diagrams(8).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(8).stroke.get.asInstanceOf[Vertex].x mustEqual 7520.0f
|
|
diagrams(8).stroke.get.asInstanceOf[Vertex].y mustEqual 6424.0f
|
|
//9
|
|
diagrams(9).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(9).stroke.get.asInstanceOf[Vertex].x mustEqual 7520.0f
|
|
diagrams(9).stroke.get.asInstanceOf[Vertex].y mustEqual 6440.0f
|
|
//10
|
|
diagrams(10).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(10).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(10).stroke.get.asInstanceOf[Vertex].y mustEqual 6448.0f
|
|
//11
|
|
diagrams(11).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(11).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(11).stroke.get.asInstanceOf[Vertex].y mustEqual 6464.0f
|
|
//12
|
|
diagrams(12).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(12).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(12).stroke.get.asInstanceOf[Vertex].y mustEqual 6472.0f
|
|
//13
|
|
diagrams(13).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(13).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(13).stroke.get.asInstanceOf[Vertex].y mustEqual 6488.0f
|
|
//14
|
|
diagrams(14).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(14).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(14).stroke.get.asInstanceOf[Vertex].y mustEqual 6496.0f
|
|
//15
|
|
diagrams(15).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(15).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(15).stroke.get.asInstanceOf[Vertex].y mustEqual 6504.0f
|
|
//16
|
|
diagrams(16).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(16).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(16).stroke.get.asInstanceOf[Vertex].y mustEqual 6512.0f
|
|
//17
|
|
diagrams(17).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(17).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(17).stroke.get.asInstanceOf[Vertex].y mustEqual 6520.0f
|
|
//18
|
|
diagrams(18).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(18).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(18).stroke.get.asInstanceOf[Vertex].y mustEqual 6528.0f
|
|
//19
|
|
diagrams(19).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(19).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(19).stroke.get.asInstanceOf[Vertex].y mustEqual 6536.0f
|
|
//20
|
|
diagrams(20).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(20).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(20).stroke.get.asInstanceOf[Vertex].y mustEqual 6544.0f
|
|
//21
|
|
diagrams(21).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(21).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(21).stroke.get.asInstanceOf[Vertex].y mustEqual 6552.0f
|
|
//22
|
|
diagrams(22).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(22).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(22).stroke.get.asInstanceOf[Vertex].y mustEqual 6560.0f
|
|
//23
|
|
diagrams(23).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(23).stroke.get.asInstanceOf[Vertex].x mustEqual 7528.0f
|
|
diagrams(23).stroke.get.asInstanceOf[Vertex].y mustEqual 6568.0f
|
|
//24
|
|
diagrams(24).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(24).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f
|
|
diagrams(24).stroke.get.asInstanceOf[Vertex].y mustEqual 6576.0f
|
|
//25
|
|
diagrams(25).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(25).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f
|
|
diagrams(25).stroke.get.asInstanceOf[Vertex].y mustEqual 6584.0f
|
|
//26
|
|
diagrams(26).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(26).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f
|
|
diagrams(26).stroke.get.asInstanceOf[Vertex].y mustEqual 6592.0f
|
|
//27
|
|
diagrams(27).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(27).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f
|
|
diagrams(27).stroke.get.asInstanceOf[Vertex].y mustEqual 6600.0f
|
|
//28
|
|
diagrams(28).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(28).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f
|
|
diagrams(28).stroke.get.asInstanceOf[Vertex].y mustEqual 6608.0f
|
|
//29
|
|
diagrams(29).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(29).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f
|
|
diagrams(29).stroke.get.asInstanceOf[Vertex].y mustEqual 6616.0f
|
|
//30
|
|
diagrams(30).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(30).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f
|
|
diagrams(30).stroke.get.asInstanceOf[Vertex].y mustEqual 6624.0f
|
|
//31
|
|
diagrams(31).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(31).stroke.get.asInstanceOf[Vertex].x mustEqual 7536.0f
|
|
diagrams(31).stroke.get.asInstanceOf[Vertex].y mustEqual 6632.0f
|
|
case _ =>
|
|
ko
|
|
}
|
|
}
|
|
|
|
"decode (style)" in {
|
|
PacketCoding.decodePacket(string_style).require match {
|
|
case BattleplanMessage(char_id, player_name, zone_id, diagrams) =>
|
|
char_id mustEqual 41378949
|
|
player_name mustEqual "Outstabulous"
|
|
zone_id mustEqual 10
|
|
diagrams.size mustEqual 3
|
|
//0
|
|
diagrams.head.action mustEqual DiagramActionCode.Style
|
|
diagrams.head.stroke.isDefined mustEqual true
|
|
diagrams.head.stroke.get.isInstanceOf[Style] mustEqual true
|
|
diagrams.head.stroke.get.asInstanceOf[Style].thickness mustEqual 3.0f
|
|
diagrams.head.stroke.get.asInstanceOf[Style].color mustEqual 2
|
|
//1
|
|
diagrams(1).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(1).stroke.get.asInstanceOf[Vertex].x mustEqual 7512.0f
|
|
diagrams(1).stroke.get.asInstanceOf[Vertex].y mustEqual 6328.0f
|
|
//2
|
|
diagrams(2).action mustEqual DiagramActionCode.Vertex
|
|
diagrams(2).stroke.get.asInstanceOf[Vertex].x mustEqual 7512.0f
|
|
diagrams(2).stroke.get.asInstanceOf[Vertex].y mustEqual 6344.0f
|
|
case _ =>
|
|
ko
|
|
}
|
|
}
|
|
|
|
"decode (message)" in {
|
|
PacketCoding.decodePacket(string_message).require match {
|
|
case BattleplanMessage(char_id, player_name, zone_id, diagrams) =>
|
|
char_id mustEqual 41378949
|
|
player_name mustEqual "Outstabulous"
|
|
zone_id mustEqual 10
|
|
diagrams.size mustEqual 1
|
|
//0
|
|
diagrams.head.action mustEqual DiagramActionCode.DrawString
|
|
diagrams.head.stroke.isDefined mustEqual true
|
|
diagrams.head.stroke.get.isInstanceOf[DrawString] mustEqual true
|
|
diagrams.head.stroke.get.asInstanceOf[DrawString].x mustEqual 7512.0f
|
|
diagrams.head.stroke.get.asInstanceOf[DrawString].y mustEqual 6312.0f
|
|
diagrams.head.stroke.get.asInstanceOf[DrawString].color mustEqual 2
|
|
diagrams.head.stroke.get.asInstanceOf[DrawString].channel mustEqual 0
|
|
diagrams.head.stroke.get.asInstanceOf[DrawString].message mustEqual "Hello Auraxis!"
|
|
case _ =>
|
|
ko
|
|
}
|
|
}
|
|
|
|
"encode (start)" in {
|
|
val msg = BattleplanMessage(
|
|
41490746,
|
|
"YetAnotherFailureAlt",
|
|
0,
|
|
BattleDiagramAction(DiagramActionCode.StartDrawing) ::
|
|
Nil
|
|
)
|
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
|
|
|
pkt mustEqual string_start
|
|
}
|
|
|
|
"encode (stop)" in {
|
|
val msg = BattleplanMessage(
|
|
41490746,
|
|
"YetAnotherFailureAlt",
|
|
0,
|
|
BattleDiagramAction(DiagramActionCode.StopDrawing) ::
|
|
Nil
|
|
)
|
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
|
|
|
pkt mustEqual string_stop
|
|
}
|
|
|
|
"encode (line)" in {
|
|
val msg = BattleplanMessage(
|
|
41378949,
|
|
"Outstabulous",
|
|
10,
|
|
BattleDiagramAction.vertex(7512.0f, 6312.0f) ::
|
|
BattleDiagramAction.vertex(7512.0f, 6328.0f) ::
|
|
BattleDiagramAction.vertex(7512.0f, 6344.0f) ::
|
|
BattleDiagramAction.vertex(7512.0f, 6360.0f) ::
|
|
BattleDiagramAction.vertex(7520.0f, 6376.0f) ::
|
|
BattleDiagramAction.vertex(7520.0f, 6392.0f) ::
|
|
BattleDiagramAction.vertex(7520.0f, 6400.0f) ::
|
|
BattleDiagramAction.vertex(7520.0f, 6416.0f) ::
|
|
BattleDiagramAction.vertex(7520.0f, 6424.0f) ::
|
|
BattleDiagramAction.vertex(7520.0f, 6440.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6448.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6464.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6472.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6488.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6496.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6504.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6512.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6520.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6528.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6536.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6544.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6552.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6560.0f) ::
|
|
BattleDiagramAction.vertex(7528.0f, 6568.0f) ::
|
|
BattleDiagramAction.vertex(7536.0f, 6576.0f) ::
|
|
BattleDiagramAction.vertex(7536.0f, 6584.0f) ::
|
|
BattleDiagramAction.vertex(7536.0f, 6592.0f) ::
|
|
BattleDiagramAction.vertex(7536.0f, 6600.0f) ::
|
|
BattleDiagramAction.vertex(7536.0f, 6608.0f) ::
|
|
BattleDiagramAction.vertex(7536.0f, 6616.0f) ::
|
|
BattleDiagramAction.vertex(7536.0f, 6624.0f) ::
|
|
BattleDiagramAction.vertex(7536.0f, 6632.0f) ::
|
|
Nil
|
|
)
|
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
|
|
|
pkt mustEqual string_line
|
|
}
|
|
|
|
"encode (style)" in {
|
|
val msg = BattleplanMessage(
|
|
41378949,
|
|
"Outstabulous",
|
|
10,
|
|
BattleDiagramAction.style(3.0f, 2) ::
|
|
BattleDiagramAction.vertex(7512.0f, 6328.0f) ::
|
|
BattleDiagramAction.vertex(7512.0f, 6344.0f) ::
|
|
Nil
|
|
)
|
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
|
|
|
pkt mustEqual string_style
|
|
}
|
|
|
|
"encode (message)" in {
|
|
val msg = BattleplanMessage(
|
|
41378949,
|
|
"Outstabulous",
|
|
10,
|
|
BattleDiagramAction.drawString(7512.0f, 6312.0f, 2, 0, "Hello Auraxis!") :: Nil
|
|
)
|
|
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
|
|
|
|
pkt mustEqual string_message
|
|
}
|
|
}
|